I'm experimenting with opencv's find contours for object detection. I want it to keep the blue portion of the python logo while masking away everything else, however my masked image is just a black window
I've tried different lower and upper boundary colours, different image forms, but everything results in a black window
import cv2
import numpy as np
img = cv2.imread('python logo.png')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) #IMG now displaying in hsv
lower_sand = np.array([84, 89, 56]) #RGB FORMAT
upper_sand = np.array([178, 175, 104]) # RGB FORMAT
mask = cv2.inRange(hsv, lower_sand, upper_sand)
cv2.imshow("Masked logo", mask)
cv2.imshow("Python Logo", img)
Related
Starting from a Minecraft image where the objective is to detect the presence of the chainmail armor, I have used filtering in masks with HSV
import numpy as np
import cv2
detect_state = False
lower_range_chainmail_armor = np.array([75, 80, 100])
upper_range_chainmail_armor = np.array([90, 255, 255])
frameHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask_chainmail_armor = cv2.inRange(frameHSV, lower_range_diamond, upper_range_diamond)
maskdiamondvis = cv2.bitwise_and(frame, frame, mask= mask_diamond)
'''
#This doesn't work because I dont need compare only with color
if(int(cv2.countNonZero(mask_chainmail_armor)) > 5000):
print("Detect chainmail armor!")
detect_state = True
'''
The problem is that it is not enough to find the gray color, but it also has to be a kind of checkered texture, as is that image
Original Image:
Binarized image with HSV filter:
How to put a variable detection to True only when it detects a checkered image like this?
I'm still very new to cv2 and python, so please forgive me if this is basic or a duplicate. I've tried searching but to no avail. I have an image, say this penguin, that I have masked using the cv2.inRange() function. I then tried to apply the Viridis colormap to the image, but it applies to everything, including the mask. I want the colormap to only apply to the unmasked region to accentuate the subtle differences in value. In essence, I want the lowest value in the unmasked region to be mapped to Viridis' purple, and the highest value in the unmasked region to be the Viridis' yellow, with the values in between linearly mapped.
Here's my attempt and resulting image. Notice how the masked, previously black region, has been included in the mapping and is now purple. I'm not even sure if it's possible. Maybe a custom colormap is needed?
import cv2
import numpy as np
img = cv2.imread('penguin.jpg') #Read in image
img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) #Change to HSV
# define range of white color in HSV
lower_white = np.array([0,0,210])
upper_white = np.array([255,255,255])
mask = cv2.inRange(img, lower_white, upper_white) # Create the mask
bitwise_mask = cv2.bitwise_and(img,img,mask=mask) # Apply mask
masked_core = cv2.cvtColor(bitwise_mask, cv2.COLOR_HSV2BGR) #Change to BGR
#But how do I get it to apply only to unmasked area?
not_desired = cv2.applyColorMap(masked_core, cv2.COLORMAP_VIRIDIS) #Viridis Colormap
#Display and write image
cv2.imwrite('penguin_masked.jpg', not_desired)
cv2.imshow('penguin', not_desired)
cv2.waitKey()
In response to your comment, perhaps this is what you want in Python/OpenCV. My answer is nearly the same except how I compute the grayscale image to be color mapped. I convert your low and high colors to intensity values. Then convert the color image to intensity grayscale. Then stretch your [0,0,210] equivalent intensity to black and your [255,255,255] equivalent intensity (255) to white with all values stretched linearly in between.
Input:
import cv2
import numpy as np
from skimage.exposure import rescale_intensity
img = cv2.imread('penguin.jpg') #Read in image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) #Change to HSV
# define range of white color in HSV
lower_white = np.array([0,0,210])
upper_white = np.array([255,255,255])
mask = cv2.inRange(hsv, lower_white, upper_white) # Create the mask
# Y = 0.299 R + 0.587 G + 0.114 B
# convert [0,0,210] to intensity = 0.299*210 = 63
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #Change to gray (intensity)
gray = rescale_intensity(gray, in_range=(63,255), out_range=(0,255)).astype(np.uint8) # linearly stretch 63 to black and 255 to white
colormap = cv2.applyColorMap(gray, cv2.COLORMAP_VIRIDIS) #Viridis Colormap to gray image
img_masked = cv2.bitwise_and(img, img, mask=mask) # Apply mask to img
colormap_masked = cv2.bitwise_and(colormap, colormap, mask=(255-mask)) # Apply inverse mask to colormapped
result = cv2.add(img_masked, colormap_masked) # Merge original and colormapped using mask
#Display and write image
cv2.imwrite('penguin_mask.jpg', mask)
cv2.imwrite('penguin_colormapped2.jpg', colormap)
cv2.imwrite('penguin_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('colormapped2', colormap)
cv2.imshow('result', result)
cv2.waitKey()
Mask:
Color Mapped Image:
Result:
Here is one way to do that in Python/OpenCV.
Read the image
Convert to HSV
Threshold the HSV using inRange() to make a mask
Convert the image to grayscale
Apply the colormap to the grayscale image
Apply the mask to the input image
Apply the inverse map to the colormapped image
Add the two masked images together to for the result
Save the result
Input:
import cv2
import numpy as np
img = cv2.imread('penguin.jpg') #Read in image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) #Change to HSV
# define range of white color in HSV
lower_white = np.array([0,0,210])
upper_white = np.array([255,255,255])
mask = cv2.inRange(hsv, lower_white, upper_white) # Create the mask
print(mask.shape, np.amax(mask))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #Change img to GRAY
colormap = cv2.applyColorMap(gray, cv2.COLORMAP_VIRIDIS) #Viridis Colormap to gray image
img_masked = cv2.bitwise_and(img, img, mask=mask) # Apply mask to img
colormap_masked = cv2.bitwise_and(colormap, colormap, mask=(255-mask)) # Apply inverse mask to colormapped
result = cv2.add(img_masked, colormap_masked) # Merge original and colormapped using mask
#Display and write image
cv2.imwrite('penguin_mask.jpg', mask)
cv2.imwrite('penguin_colormapped.jpg', colormap)
cv2.imwrite('penguin_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('colormapped', colormap)
cv2.imshow('result', result)
cv2.waitKey()
Mask Image:
Colormapped Image:
Result:
NOTE: If I have misunderstood and you want the penguin color mapped, then just swap the mask and the inverse mask in the bitwise_and operations.
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]])
I'm working on extracting the highlighted text from a text book. I have already done locating the highlight and extracting the text inside. To deal with the highlight, I converted the image to grayscale and used OTSU threshold to remove the background highlighted color.
This works great when the highlight is a light color like yellow or green but when the highlight is a dark color, the thresholding fails and i get black background covering most of the text which hinders the ocr reading.
I have tried normalising the brightness but it does not seem to work.
What I need is some way to determine the foreground and background color and then remove the background color. Or I need some way to dynamically threshold the image to get black text and white background.
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
normalized_gray = cv2.equalizeHist(gray)
(thresh, processed_image) = cv2.threshold(normalized_gray, 127, 255, cv2.THRESH_OTSU)
The test image:
https://ibb.co/856YtMx
Some test result:
When i run equalizeHist before thresholding.
https://ibb.co/HT0jpKW
When i run equalizeHist after thresholding.
https://ibb.co/ZXSz97J
When i use a Binary threshold, the text are blown away:
https://ibb.co/DLXywXz
e.g. something like this should work:
import cv2
import numpy as np
image = cv2.imread('photo-2019-08-12-12-44-59.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# adjust contrast
gray_contract = cv2.multiply(gray, 1.5)
# create a kernel for the erode
kernel = np.ones((2, 2), np.uint8)
img_eroded = cv2.erode(gray_contract, kernel, iterations=1)
# binarize with otsu
(thresh, otsu) = cv2.threshold(img_eroded, 127, 255,
cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Also you can have a look at post How to remove shadow from scanned images using OpenCV
Adaptive threshold is what you need here.
My output with code. Can be fine tuned.
import cv2
import numpy as np
img = cv2.imread("high.jpg")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gaus = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 20)
cv2.imshow("Gaussian", gaus)
cv2.waitKey(0)
cv2.imwrite('output.png', gaus)
UPDATE
Changed parameters to the adaptiveThreshold function, the second image you posted.
gaus = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 8)
How can we replace all the black color from a video to white.
I tried this but its not working
import cv2
import numpy as np
from matplotlib import pyplot as plt
cap = cv2.VideoCapture(0)
while(1):
ret, img = cap.read()
#Remove all black color and replace it with white
img[np.where((img == [0,0,0]).all(axis = 2))] = [255,255,255]
The following code snippet shows how to replace all black pixels in a BGR image with white using only Numpy. You can apply it to each frame of your video.
import numpy as np
a = np.zeros((100,100, 3), dtype= np.uint8) # Original image. Here we use a black image but you can replace it with your video frame.
white = np.full((100, 100, 3), 255, dtype=np.uint8) # White image
a = np.where(a[:,:] == [0,0,0], white, a) # This is where we replace black pixels.
Please note that this will only replace true black pixels. In a real-world video, you'd do some thresholding as a pre-processing step as it is very rare to have perfectly black pixels.
EDIT:
To affect dark pixels under a certain value but not necessarily completely black, use thresholding:
# We make all pixels with value lower than 100 into black pixels.
# This only works with grayscale images.
a = cv2.cvtColor(a, cv.COLOR_BGR2GRAY) # Convert image to grayscale
ret, a = cv2.threshold(a, 100, 255, cv2.THRESH_TOZERO) # Make gray pixels under 100 black.
This only works for grayscale images but maybe you should consider converting your image from BGR to grayscale in any case if your goal is to detect black pixels to turn them into white.