I have a picture were I want to change all white-ish pixels to grey, but only for a certain area of the image. Example picture, I just want to change the picture outside of the red rectangle, without changing the image within the red rectangle:
I already have the general code, which was part of someone elses Stackoverflow question, that changes the colour of every white pixel instead of only just the one outside of an area.
image = cv.imread("meme 2.jpg")
hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])
# Mask image to only select white
mask = cv.inRange(hsv, lower_white, upper_white)
# Change image to grey where we found brown
image[mask > 0] = (170, 170, 170)
cv.imwrite(file, image)
Here is one way to do that in Python/OpenCV.
Read the input
Convert to HSV color space
Threshold on desired color to make a mask
Use the mask to change the color of all corresponding pixels in the image
Draw a new rectangular mask for the region where you do not want to change
Invert the new mask for the region where you do want to change
Apply the new mask to the original image
Apply the inverted new mask to the color changed image
Add the two results together to form the final image
Save the results
Input:
import cv2
import numpy as np
# Read image
image = cv2.imread('4animals.jpg')
# Convert to HSV
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])
# Create mask to only select white
mask = cv2.inRange(hsv, lower_white, upper_white)
# Change image to grey where we found white
image2 = image.copy()
image2[mask > 0] = (170, 170, 170)
# Create new rectangular mask that is white on black background
x,y,w,h = 33,100,430,550
mask2 = np.zeros_like(image)
cv2.rectangle(mask2, (x,y), (x+w,y+h), (255, 255, 255), -1)
# invert mask
mask2_inv = 255 - mask2
# apply mask to image
image_masked = cv2.bitwise_and(image, mask2)
# apply inverted mask to image2
image2_masked = cv2.bitwise_and(image2, mask2_inv)
# add together
result = cv2.add(image_masked, image2_masked)
# save results
cv2.imwrite('4animals_mask.jpg', mask)
cv2.imwrite('4animals_modified.png', image2)
cv2.imwrite('4animals_mask2.jpg', mask2)
cv2.imwrite('4animals_mask2_inv.jpg', mask2_inv)
cv2.imwrite('4animals_masked.jpg', image_masked)
cv2.imwrite('4animals_modified_masked.jpg', image2_masked)
cv2.imwrite('4animals_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('image2', image2)
cv2.imshow('mask2', mask2 )
cv2.imshow('mask2_inv', mask2_inv)
cv2.imshow('image_masked', image_masked)
cv2.imshow('image2_masked', image2_masked)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Color mask:
Rectangle mask:
Inverted rectangle mask:
Color changed image:
Masked input:
Masked color changed image:
Result:
Here is another simpler method in Python/OpenCV. My previous answer was overly complicated.
Read the input
Convert to HSV color space
Create a mask image by color thresholding
Draw a black rectangle on the previous mask for where you do not want to change the color
Apply the new combined mask to the image to change the color in the desired region
Save the result
Input:
import cv2
import numpy as np
# Read image
image = cv2.imread('4animals.jpg')
# Convert to HSV
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Define lower and uppper limits of what we call "white-ish"
sensitivity = 19
lower_white = np.array([0, 0, 255 - sensitivity])
upper_white = np.array([255, sensitivity, 255])
# Create mask to only select white
mask = cv2.inRange(hsv, lower_white, upper_white)
# Draw new rectangular mask on old mask that is black inside the rectangle and white outside the rectangle
x,y,w,h = 33,100,430,550
mask2 = mask.copy()
cv2.rectangle(mask2, (x,y), (x+w,y+h), 0, -1)
# Change image to grey where we found white for combined mask
result = image.copy()
result[mask2 > 0] = (170, 170, 170)
# save results
cv2.imwrite('4animals_mask.jpg', mask)
cv2.imwrite('4animals_mask2.jpg', mask2)
cv2.imwrite('4animals_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('mask2', mask2 )
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask from color thresholding:
Modified mask with rectangle drawn over it:
Result:
Related
all of you,
Here is the image;
The exact background color is in RGB; (246, 46, 100)
I have tried several methods but those are too slow, one of the methods is below;
new_image = Image.open("image-from-rawpixel-id-6649116-original.png")
img_up = np.asarray(new_image)
for ind1, i in enumerate(tqdm(img_up)):
for ind2, i2 in enumerate(i):
if list(i2[:3]) != a:
img_up2 = img_up.copy()
img_up2.setflags(write=1)
img_up2[ind1][ind2][:3] = [0,0,0]
cv2.imshow('', img_up2)
cv2.waitKey()
I want to make the background white and the foreground person black (masked), but unable to find a quick method.
Modified
I have tied another method to mask the foreground but I think, I am doing some mistakes while converting between RGBs. Below is the code;
path = 'image-from-rawpixel-id-2923073-png.png'
im = Image.open(path).convert('RGBA')
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
#--------------------------- To change the background color that is not present in the foreground ---------------------------------------------
lst_ch_a = []
for pixel in tqdm(im.getdata()):
lst_ch_a.append(pixel[:3])
break_out = True
while break_out:
a = random.sample(range(0, 255), 3)
if a not in lst_ch_a:
new_image = Image.new("RGBA", im.size, tuple(a))
print(tuple(a))
break_out = False
new_image.paste(im, mask=im)
new_image.convert("RGB").save("check6.jpg")
#--------------------------- Numpy ----------------------------------------------------------------
img_up = np.asarray(new_image)
img = img_up.copy()
img.setflags(write=1)
img[:,:,:3][img[:,:,:3] != tuple(a)] = 0
img[img!=0]=255
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
img = cv2.resize(img,(500,500), cv2.INTER_LINEAR)
cv2.imwrite('results1.jpg', img)
cv2.imshow('', img)
cv2.waitKey(0)
Below is the result, but I am getting pixels of Blue, green, red, and some other colors in the image. do you know why I am getting this?
You can see in the above first image, where I have changed the background. The image was transparent image, but then I changed the background color. There was no green, blue, or red colors but while masking the foreground the red, blue, and green color emerges.
Do you know why it is happening?
First you could read image using cv2.imread() and you get directly numpy.array.
You can use numpy image[ mask ] = [0,0,0] to assign value to many pixels in milliseconds.
For exact color you can create mask using img == (100, 46, 247).
cv2 keeps image as BGR instead of RGB so it needs (100,46,247) instead of (247,46,100).
It needs also .all(axis=-1) because it compares every value B,G,R separatelly and gets tuples (True, True, False) but it needs to reduce it to single True when all values are True.
import cv2
img = cv2.imread("image.png")
#print('color:', img[0, 0]) # [100 46 247]
mask = (img == (100, 46, 247)).all(axis=-1)
img1 = img.copy()
img2 = img.copy()
img1[ mask ] = [0,0,0]
img2[ ~mask ] = [0,0,0]
cv2.imshow('image1', img1)
cv2.imshow('image2', img2)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.imwrite('image2-1.png', img1)
cv2.imwrite('image2-2.png', img2)
Result:
image1:
image2:
BTW:
cv2 has function inRange() to select colors in some ranges and it may give better result.
Example code but I didn't find good range for this image.
Besides it starts removing similar pixels in lips.
import cv2
import numpy as np
img = cv2.imread("image.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#print('hsv:', hsv[0, 0])
lower = np.array([92, 120, 147])
upper = np.array([172, 208, 247]) # for BGR (100, 46, 247) - value from hsv[0,0]
upper = np.array([202, 218, 247])
mask = cv2.inRange(hsv, lower, upper)
print('masking')
# `mask==255` `mask==0`
#img = cv2.bitwise_and(img, img, mask=~mask) # OK
#img[np.where(mask==255)] = [0,0,0] # OK
img[ mask==255 ] = [0,0,0] # OK
#img[ mask.astype(bool) ] = [0,0,0] # OK
#img[ mask ] = [0,0,0] # WRONG (hangs)
print('display')
#h, w = img.shape[:2]
#img = cv2.resize(img, (h//5, w//5))
cv2.imshow('image', img)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.imwrite('image2b.jpg', img)
See also: Finding red color in image using Python & OpenCV - Stack Overflow
You should be able to do this more effectively with PIL.ImageDraw.floodfill() which is described here.
If you use a global replacement, you will catch reddish tones (such as the model's lips) anywhere and everywhere they occur in the image. If you use a floodfill from the top-left corner with a suitable tolerance, the flooding should get stopped by her dark hair before it can contaminate her lips.
The result should be like this:
I actually did that with ImageMagick equivalent operator in Terminal as I don't currently have Python to hand:
magick IXVJl.jpg -fuzz 30% -fill white -draw "color 0,0 floodfill" result.jpg
I have this image.
I want to make all the colored headings to white and the text in it to black.
I try below to make the image full black and white.
img_grey = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
thresh = 170
img_binary = cv2.threshold(img_grey, thresh, 250, cv2.THRESH_BINARY)[1]
cv2.imwrite('bw_img.jpg',img_binary)
Now those headings are black and text within those is white. But i want to make text black and heading layout white. So, can anyone helps me with that?
You may convert the image to HSV, apply threshold to find colored regions, and copy the result of cv2.threshold with cv2.THRESH_BINARY_INV only to the colored regions.
Main stages of the suggested solution:
Convert from BGR to HSV color space, and get the saturation color channel.
all black and white are zero, and colored pixels are above zero.
Apply threshold to the saturation channel.
Find contours on the binarized saturation channel.
Draw the contours as white (255 values) on black background to form a mask.
Apply morphological closing for closing some black gaps.
Get only the area inside the mask from img_binary_inv (result of your code using cv2.THRESH_BINARY_INV).
Copy masked img_binary_inv to img_grey only in pixels that mask is white.
Complete code sample:
import numpy as np
import cv2
img_bgr = cv2.imread('img.jpg') # Read image as BGR
img_grey = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # Convert from BGR to grayscale.
thresh = 170
img_binary_inv = cv2.threshold(img_grey, thresh, 255, cv2.THRESH_BINARY_INV)[1] # Apply threshold and invert black/white
# Convert from BGR to HSV color space.
hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# Get the saturation color channel - all black and white are zero, and colored pixels are above zero.
s = hsv[:, :, 1]
thresh = 100
s_binary = cv2.threshold(s, thresh, 255, cv2.THRESH_BINARY)[1] # Apply threshold to the saturation channel.
# Find contours on s_binary
cnts = cv2.findContours(s_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2] # Use index [-2] to be compatible to OpenCV 3 and 4
# Draw the contours as white (255 values) on black background.
mask = np.zeros_like(s_binary)
cv2.drawContours(mask, cnts, -1, 255, -1)
# Apply morphological closing for closing some black gaps.
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((3, 3)))
# Get only the area inside the mask from img_binary_inv
masked_binary_inv = cv2.bitwise_or(img_binary_inv, img_binary_inv, mask=mask)
# Copy masked_binary_inv to img_grey only in pixels that mask is white.
cv2.copyTo(masked_binary_inv, mask, img_grey)
cv2.imwrite('img_grey.png', img_grey) # Save result (as PNG and not JPEG for better quality).
# Show images
cv2.imshow('img_bgr', img_bgr)
cv2.imshow('s_binary', s_binary)
cv2.imshow('mask', mask)
cv2.imshow('masked_binary_inv', masked_binary_inv)
cv2.imshow('img_grey', img_grey)
cv2.waitKey()
cv2.destroyAllWindows()
Result (img_grey):
The result doesn't look so good, due to the relatively low quality of the input image.
Intermediate results:
s_binary:
mask:
masked_binary_inv:
I want to blur red color in image ("1.png" is attached) so that its not clearly visible. I tried below code where I can change red color to black color but how can I blur it? Please help.
import cv2
import numpy as np
frame = cv2.imread("1.png")
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# define range of red color in HSV
lower = np.array([0,50,50])
upper = np.array([10,255,255])
# define range of blue color in HSV
# lower = np.array([38, 86, 0])
# upper = np.array([121, 255, 255])
# define range of pink color in HSV
# http://www.workwithcolor.com/pink-color-hue-range-01.htm
# lower = np.array([158, 127, 0])
# upper = np.array([179, 255, 255])
# Threshold the HSV image to get only red colors
mask = cv2.inRange(hsv, lower, upper)
color_only = cv2.bitwise_and(frame, frame, mask = mask)
# convert mask to 3-channel image to perform subtract
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
res = cv2.subtract(frame, mask) #negative values become 0 -> black
cv2.imshow("frame", frame)
# cv2.imshow("mask", mask)
# cv2.imshow("color_only", color_only)
cv2.imshow("res", res)
cv2.waitKey()
cv2.destroyAllWindows()
1.png
You need to blur the segmented image and then use alpha blending to composite the blurred ROI with the background image. This code takes you through all the steps:
Read image and segment the color of interest:
import cv2
import numpy as np
frame = cv2.imread("/home/stephen/Desktop/1.png")
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# define range of red color in HSV
lower = np.array([0,50,50])
upper = np.array([10,255,255])
mask = cv2.inRange(hsv, lower, upper)
color_only = cv2.bitwise_and(frame, frame, mask = mask)
### THE BACKGROUND MUST BE MADE WHITE, NOT BLACK ###
color_only[np.where((color_only==[0,0,0]).all(axis=2))] = [255,255,255]
cv2.imshow("color_only", color_only)
Next, blur the segmented image. Note, I am using a 13,13 kernel to blur the image:
blur = cv2.blur(color_only, (13,13))
cv2.imshow('blur', blur)
Next, blur the mask of the segmented image. We are going to use this to combine the images later. It's easy to combine images using a bitwise function, but that approach will not work here because the blurred image no longer occupies the same space as the segmented image:
maskForAlphaBlending = blur
## BLACK OUT THE WHITE BACKGROUND OF THE ALPHA MASK
maskForAlphaBlending[np.where((maskForAlphaBlending==[255,255,255]).all(axis=2))] = [0,0,0]
maskForAlphaBlending = cv2.cvtColor(maskForAlphaBlending, cv2.COLOR_BGR2GRAY)
cv2.imshow('maskForAlphaBlending', maskForAlphaBlending)
Finally alpha blending can be used to composite the blurred segmented image and the green and white background image:
foreground = blur
background = frame
alpha = cv2.cvtColor(maskForAlphaBlending, cv2.COLOR_GRAY2BGR)
foreground = foreground.astype(float)
background = background.astype(float)
alpha = alpha.astype(float)/100
foreground = cv2.multiply(alpha, foreground)
background = cv2.multiply(1.0 - alpha, background)
outImage = cv2.add(foreground, background)
cv2.imshow("outImg", outImage/255)
Note how the red lines are blurred, but the green and white border is not:
I have this image of a bunch of circles, all different colors (red, green, yellow, purple, etc.). I would like to individually crop all the red circles and save them as separate files (ex. circle(1).png, circle(2).png, etc.).
What I have so far is a solution to only show the red circles. I created a mask with cv2.inRange and used a cv2.bitwise_and to only show the red circles. Here is my code:
import cv2
import numpy as np
image = cv2.imread('dots.jpg')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower_red = np.array([150,100,0])
upper_red = np.array([255,255,255])
# Threshold the HSV image to get only red cirlces
mask = cv2.inRange(hsv, lower_red, upper_red)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(image,image, mask=mask)
I guess what I'm looking for is something like cv2.selectROI() but runs automatically (no manual click&drag) and can crop multiple regions. Any ideas or tips appreciated. Thanks
For red, you can choose the HSV range (0,50,20) ~ (5,255,255) and (175,50,20)~(180,255,255) using the colormap given here. Your mask in above code won't detect both red circles in below image, for example. Check this yourself.
You can try below code:
import cv2
import numpy as np
image = cv2.imread('circles.jpg')
img_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Gen lower mask (0-5) and upper mask (175-180) of RED
mask1 = cv2.inRange(img_hsv, (0,50,20), (5,255,255))
mask2 = cv2.inRange(img_hsv, (175,50,20), (180,255,255))
# Merge the mask and crop the red regions
mask = cv2.bitwise_or(mask1, mask2)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(image,image, mask=mask)
# coverting image with red colored region of interest from HSV to RGB
hsv2bgr = cv2.cvtColor(res, cv2.COLOR_HSV2BGR)
# RGB to GRAYSCALE
rgb2gray = cv2.cvtColor(hsv2bgr, cv2.COLOR_BGR2GRAY)
# Applying thresholding to the grayscale image for black & white color
thresh_gray = cv2.threshold(rgb2gray, 20,255, cv2.THRESH_BINARY)[1]
# Find the different contours
contours = cv2.findContours(rgb2gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[0]
#print(len(contours))
i = 0
for c in contours:
_, radius = cv2.minEnclosingCircle(c)
if radius>10:
# create a mask and fill it with white color
mask = np.zeros(image.shape, dtype=np.uint8)
cv2.fillPoly(mask, pts=[c], color=(255, 255, 255))
# Bitwise-AND mask and original image
# output is red circle with black background
masked_image = cv2.bitwise_and(image, mask)
# to get individual red circle with white background
mask_ = cv2.bitwise_not(mask)
circle_ = cv2.bitwise_or(masked_image, mask_)
cv2.imwrite('circle({}).jpg'.format(i), circle_)
i+=1
Input Image: circles.jpg
There are two red circle object in the above input image, hence it will create two files- circle(0).jpg and circle(1).jpg each with individual red circles.
I'm trying to select the green color in an image using OpenCV (the method to do it comes from this website. The image I'm treating is :
Here is the code I tried to write.
import cv2
import matplotlib.pyplot as plt
import numpy as np
greenhsv = (60, 255, 255)
green2hsv=(70,100,170)
g_square = np.full((10, 10, 3), greenhsv, dtype=np.uint8)/255.0
plt.imshow(hsv_to_rgb(g_square))
plt.show()
g1_square = np.full((10, 10, 3), green2hsv, dtype=np.uint8)/255.0
plt.imshow(hsv_to_rgb(g1_square))
plt.show()
nucl = cv2.imread('./Pictures/image_nucleation_essai0.png')
nucl = cv2.cvtColor(nucl, cv2.COLOR_BGR2RGB)
plt.imshow(nucl)
plt.show()
hsv_nucl = cv2.cvtColor(nucl, cv2.COLOR_RGB2HSV)
mask = cv2.inRange(hsv_nucl, greenhsv,green2hsv)
result = cv2.bitwise_and(nucl, nucl, mask=mask)
plt.imshow(mask, cmap="gray")
plt.show()
plt.imshow(result)
plt.show()
The result is :
So the mask did not work.
Your color ranges are not quite right yet. Also the variables in the inRange() function are in the wrong order. It's from-to, so the darker color must be first. Change your code to cv2.inRange(hsv_nucl, green2hsv,greenhsv) You can use/tweak the values in the code below, that works.
Result:
With white background:
import numpy as np
import cv2
# load image
img = cv2.imread("Eding.png")
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# set lower and upper color limits
lower_val = np.array([50,100,170])
upper_val = np.array([70,255,255])
# Threshold the HSV image to get only green colors
mask = cv2.inRange(hsv, lower_val, upper_val)
# apply mask to original image - this shows the green with black blackground
only_green = cv2.bitwise_and(img,img, mask= mask)
# create a black image with the dimensions of the input image
background = np.zeros(img.shape, img.dtype)
# invert to create a white image
background = cv2.bitwise_not(background)
# invert the mask that blocks everything except green -
# so now it only blocks the green area's
mask_inv = cv2.bitwise_not(mask)
# apply the inverted mask to the white image,
# so it now has black where the original image had green
masked_bg = cv2.bitwise_and(background,background, mask= mask_inv)
# add the 2 images together. It adds all the pixel values,
# so the result is white background and the the green from the first image
final = cv2.add(only_green, masked_bg)
#show image
cv2.imshow("img", final)
cv2.waitKey(0)
cv2.destroyAllWindows()