Change a defined colour in an image using Python - python

I have an image containing a girl's face and I would like to change the colour of her eyes starting from an identified colour using the RGB in python. I have this colour:
rbg_source=[85, 111, 47]
and this colour as the destination
rbg_destination=[72.50445669, 56.82411376, 47.7519902]
reconstruction the image as the original one substituting only the mentioned colours.
Do you have an idea to do that in python?
I have already used the following solution:
resized_image[np.all(resized_image == (85, 111, 47), axis=-1)] = (72.50445669, 56.82411376,
47.7519902)
# Save result
cv2.imwrite('result1.png',resized_image)
But it returns a bad image without the expect solution.
Please find below an example image
in that image, I would like to change the right eye colour knowing the RGB of that colour i.e. (5, 155, 122)

Here is one way to do that in Python/OpenCV, since I do not have the corresponding RGB colors for the blue and green that you want to use. So I will approximate using baseline blue and green.
Read input
Convert to HSV and separate channels
Specify blue and green hues and get hue difference
Threshold on green color range to make a mask
Use morphology to clean the mask
Add the hue difference to the hue channel and modulo 180
Combine the new hue, the old saturation channel and the old value channel that is biased to increase brightness to match the left eye color and convert back to BGR
Use the mask to merge the new BGR and the original image
Save the results
Input:
import cv2
import numpy as np
# load image with alpha channel
img = cv2.imread('eyes.png')
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
# blue is 240 in range 0 to 360; so half in OpenCV
# green is 120 in range 0 to 360; so half in OpenCV
blue_hue = 120
green_hue = 60
# diff hue (blue_hue - green_hue)
diff_hue = blue_hue - green_hue
# create mask for green color in hsv
lower = (30,90,90)
upper = (90,170,180)
mask = cv2.inRange(hsv, lower, upper)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels and bias value to make brighter
hsv_new = cv2.merge([hnew,s,v+60])
# 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('eyes_green_mask.png', mask)
cv2.imwrite('eyes_green2blue.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
Hue and Value shifted BGR image:
Result:
ADDITION:
If you know the exact blue and green BGR colors, you can convert them each to HSV and get the H,S,V differences. Then use those differences as biases to the H,S,V channels of the input image and use the mask to combine that result with the original as I did above.

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:

Color percentage in image for Python using OpenCV

I'm creating a code which can detect the percentage of green colour from an image.
.
I have a little experience with OpenCV but am still pretty new to image processing and would like some help with my code. How should I change this code so that it is capable of calculating the percentage of green instead of brown? And if it isn't too troublesome could someone please explain how the changes affect the code? Below is the link to the image I would like to use.
Credit for the code goes to #mmensing
import numpy as np
import cv2
img = cv2.imread('potato.jpg')
brown = [145, 80, 40] # RGB
diff = 20
boundaries = [([brown[2]-diff, brown[1]-diff, brown[0]-diff],
[brown[2]+diff, brown[1]+diff, brown[0]+diff])]
for (lower, upper) in boundaries:
lower = np.array(lower, dtype=np.uint8)
upper = np.array(upper, dtype=np.uint8)
mask = cv2.inRange(img, lower, upper)
output = cv2.bitwise_and(img, img, mask=mask)
ratio_brown = cv2.countNonZero(mask)/(img.size/3)
print('brown pixel percentage:', np.round(ratio_brown*100, 2))
cv2.imshow("images", np.hstack([img, output]))
cv2.waitKey(0)
I've modified your script so you can find the (approximate) percent of green color in your test images. I've added some comments to explain the code:
# Imports
import cv2
import numpy as np
# Read image
imagePath = "D://opencvImages//"
img = cv2.imread(imagePath+"leaves.jpg")
# Here, you define your target color as
# a tuple of three values: RGB
green = [130, 158, 0]
# You define an interval that covers the values
# in the tuple and are below and above them by 20
diff = 20
# Be aware that opencv loads image in BGR format,
# that's why the color values have been adjusted here:
boundaries = [([green[2], green[1]-diff, green[0]-diff],
[green[2]+diff, green[1]+diff, green[0]+diff])]
# Scale your BIG image into a small one:
scalePercent = 0.3
# Calculate the new dimensions
width = int(img.shape[1] * scalePercent)
height = int(img.shape[0] * scalePercent)
newSize = (width, height)
# Resize the image:
img = cv2.resize(img, newSize, None, None, None, cv2.INTER_AREA)
# check out the image resized:
cv2.imshow("img resized", img)
cv2.waitKey(0)
# for each range in your boundary list:
for (lower, upper) in boundaries:
# You get the lower and upper part of the interval:
lower = np.array(lower, dtype=np.uint8)
upper = np.array(upper, dtype=np.uint8)
# cv2.inRange is used to binarize (i.e., render in white/black) an image
# All the pixels that fall inside your interval [lower, uipper] will be white
# All the pixels that do not fall inside this interval will
# be rendered in black, for all three channels:
mask = cv2.inRange(img, lower, upper)
# Check out the binary mask:
cv2.imshow("binary mask", mask)
cv2.waitKey(0)
# Now, you AND the mask and the input image
# All the pixels that are white in the mask will
# survive the AND operation, all the black pixels
# will remain black
output = cv2.bitwise_and(img, img, mask=mask)
# Check out the ANDed mask:
cv2.imshow("ANDed mask", output)
cv2.waitKey(0)
# You can use the mask to count the number of white pixels.
# Remember that the white pixels in the mask are those that
# fall in your defined range, that is, every white pixel corresponds
# to a green pixel. Divide by the image size and you got the
# percentage of green pixels in the original image:
ratio_green = cv2.countNonZero(mask)/(img.size/3)
# This is the color percent calculation, considering the resize I did earlier.
colorPercent = (ratio_green * 100) / scalePercent
# Print the color percent, use 2 figures past the decimal point
print('green pixel percentage:', np.round(colorPercent, 2))
# numpy's hstack is used to stack two images horizontally,
# so you see the various images generated in one figure:
cv2.imshow("images", np.hstack([img, output]))
cv2.waitKey(0)
Output:
green pixel percentage: 89.89
I've produced some images, this is the binary mask of the green color:
And this is the ANDed out of the mask and the input image:
Some additional remarks about this snippet:
Gotta be careful loading images with OpenCV, as they are loaded in
BGR format rather than the usual RGB. Here, the snippet has this
covered by reversing the elements in the boundary list, but keep an
eye open for this common pitfall.
Your input image was too big to even display it properly using
cv2.imshow. I resized it and processed that instead. At the end,
you see I took into account this resized scale in the final percent
calculation.
Depending on the target color you define and the difference you
use, you could be producing negative values. In this case, for
instance, for the R = 0 value, after subtracting diff you would
get -20. That doesn't make sense when you are encoding color
intensity in unsigned 8 bits. The values must be in the [0, 255] range.
Watch out for negative values using this method.
Now, you may see that the method is not very robust. Depending on what you are doing, you could switch to the HSV color space to get a nicer and more accurate binary mask.
You can try the HSV-based mask with this:
# The HSV mask values, defined for the green color:
lowerValues = np.array([29, 89, 70])
upperValues = np.array([179, 255, 255])
# Convert the image to HSV:
hsvImage = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Create the HSV mask
hsvMask = cv2.inRange(hsvImage, lowerValues, upperValues)
# AND mask & input image:
hsvOutput = cv2.bitwise_and(img, img, mask=hsvMask)
Which gives you this nice masked image instead:

Apply a colormap only to unmasked region (excluding the black mask)

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 can't get the patched regions of a citrus fruit using Otsu method with the Green channel on opencv

I'm trying to get the patched regions of a citrus fruit using Otsu method with opencv. According to this paper: https://www.intechopen.com/books/agricultural-robots-fundamentals-and-applications/multimodal-classification-of-mangoes/ these authors were using the Green channel (G) to get the patches regions of mangoes:
I'm doing the same but usign lemons but I can't get those regions of my lemon.
This is my input image:
First I read the image and I'm calling to a function to show the image:
def show(img, titulo):
plt.figure(figsize=(7,7))
plt.title(titulo)
plt.imshow(img)
plt.show()
#read img
file = "lemons/bad/bad_5.jpg"
image = cv2.imread(file)
#convert from BGR to RGB
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
original = image
show(image, "original img "+str(image.shape))
Then I Added a blur filter:
#(blur) filter
image = cv2.blur(image,(31,31),0)
show(image, "img with BLUR")
Convert to HSV:
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#get hsv channels
h, s, v = cv2.split(hsv)
show(s, "channel S of HSV")
Then I added the Otsu's method:
#OTSU
_, thr = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show(thr, "Binarized image with the OTSU method")
Finally I put this Otsu mask to my original image:
result = cv2.bitwise_and(original, original, mask=thr)
show(result, "Lemon Segmented")
From here, with this only hole there is not problems because I'm getting the entire image.
According the research of the below URL https://www.intechopen.com/books/agricultural-robots-fundamentals-and-applications/multimodal-classification-of-mangoes/ it says that to isolate the patched regions, we should get the Green channel:
image = gray
B,green_ch,R = cv2.split(result)
show(green_ch, "Green channel 'G'")
This is the output:
Here there is a notary visualization of the two patched regions but when I use this channel to apply Otsu method, there is not results instead of I'm getting black holes:
_, thr = cv2.threshold(green_ch, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show(thr, "OTSU with Green channel")
Result:
result = cv2.bitwise_and(original, original, mask=thr)
show(result, "Lemon Segmented")
As we can see the two patched regions are not segmented using the green channel. I tryed using the HSL color space but there is not good results. My idea is detect this patched and the get the color histogram to then train a classifier using those features.
I left this second image were I was testing the code:
Well guys I would like to see your suggestions to try to get the same result the paper of above, any I idea I will apreciate it.
Thanks so much.
Edit: I inverted the second mask to get just the defect areas.
Once you use otsu's the first time it'll give you back a mask that separates the foreground (the fruit) and the background. You can use otsu's a second time on the masked area to get another mask that separates out the dark spots on the fruit.
Unfortunately, OpenCV doesn't have a simple way of running otsu's on just a masked area. However, otsu's is just looking for thresholds on the pixel intensity histogram that create the greatest interparty variance. Since this histogram is all proportional, we can force otsu's to run on just the masked area by making all of the unmasked pixels match the histogram propotions.
I converted to HSV and used the saturation channel to separate the fruit from the background.
I then used the histogram to replicate the pixel proportions on the unmasked pixels of the hue channel.
Hue Before
Hue After
Then I run otsu's a second time on the hue channel.
Now to get the final mask, we just bitwise_and the first and second masks together (and do an opening and closing operation to clean up little holes)
import cv2
import numpy as np
import random
# apply histogram
def applyHist(gray, mask, hist):
# get cumulative distribution
cumulative = [];
total = 0;
for val in hist:
total += val;
cumulative.append(total);
# apply to each pixel not in max
positions = np.where(mask != 255);
for a in range(len(positions[0])):
# choose value
rand = random.randint(0, cumulative[-1]);
index = 0;
while rand > cumulative[index]:
index += 1;
# apply
y = positions[0][a];
x = positions[1][a];
gray[y,x] = index;
# load image
img = cv2.imread("lemon.png");
# hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
# use the saturation channel for the first mask
s = cv2.GaussianBlur(s, (5,5), 0);
_, mask = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
# grab positions from mask and make histogram
positions = np.where(mask == 255);
hist = [0 for a in range(256)];
for a in range(len(positions[0])):
y = positions[0][a];
x = positions[1][a];
pix = h[y,x];
hist[pix] += 1;
# opencv doesn't have a way to just let you otsu on a mask...
# eheheheheheh, AHAHAHAHAHA
# LET'S JUST MAKE THE REST OF THE IMAGE MATCH THE HISTOGRAM
applyHist(h, mask, hist);
# otsu the image
h = cv2.GaussianBlur(h, (5,5), 0);
_, second_mask = cv2.threshold(h, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
second_mask = cv2.bitwise_not(second_mask); # just get the defects
# combine with first mask
final_mask = cv2.bitwise_and(mask, second_mask);
# opening and closing to get rid of small holes
kernel = np.ones((3,3), np.uint8);
# closing
final_mask = cv2.dilate(final_mask, kernel, iterations = 2);
final_mask = cv2.erode(final_mask, kernel, iterations = 2);
# opening
final_mask = cv2.erode(final_mask, kernel, iterations = 2);
final_mask = cv2.dilate(final_mask, kernel, iterations = 2);
# mask the image
cropped = np.zeros_like(img);
cropped[final_mask == 255] = img[final_mask == 255];
# show image
cv2.imshow("image", img);
cv2.imshow("cropped", cropped);
cv2.imshow("final", final_mask);
cv2.waitKey(0);

How to multiply two image in Python? [duplicate]

How can I apply mask to a color image in latest python binding (cv2)? In previous python binding the simplest way was to use cv.Copy e.g.
cv.Copy(dst, src, mask)
But this function is not available in cv2 binding. Is there any workaround without using boilerplate code?
Here, you could use cv2.bitwise_and function if you already have the mask image.
For check the below code:
img = cv2.imread('lena.jpg')
mask = cv2.imread('mask.png',0)
res = cv2.bitwise_and(img,img,mask = mask)
The output will be as follows for a lena image, and for rectangular mask.
Well, here is a solution if you want the background to be other than a solid black color. We only need to invert the mask and apply it in a background image of the same size and then combine both background and foreground. A pro of this solution is that the background could be anything (even other image).
This example is modified from Hough Circle Transform. First image is the OpenCV logo, second the original mask, third the background + foreground combined.
# http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghcircles/py_houghcircles.html
import cv2
import numpy as np
# load the image
img = cv2.imread('E:\\FOTOS\\opencv\\opencv_logo.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# detect circles
gray = cv2.medianBlur(cv2.cvtColor(img, cv2.COLOR_RGB2GRAY), 5)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=50, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
# draw mask
mask = np.full((img.shape[0], img.shape[1]), 0, dtype=np.uint8) # mask is only
for i in circles[0, :]:
cv2.circle(mask, (i[0], i[1]), i[2], (255, 255, 255), -1)
# get first masked value (foreground)
fg = cv2.bitwise_or(img, img, mask=mask)
# get second masked value (background) mask must be inverted
mask = cv2.bitwise_not(mask)
background = np.full(img.shape, 255, dtype=np.uint8)
bk = cv2.bitwise_or(background, background, mask=mask)
# combine foreground+background
final = cv2.bitwise_or(fg, bk)
Note: It is better to use the opencv methods because they are optimized.
import cv2 as cv
im_color = cv.imread("lena.png", cv.IMREAD_COLOR)
im_gray = cv.cvtColor(im_color, cv.COLOR_BGR2GRAY)
At this point you have a color and a gray image. We are dealing with 8-bit, uint8 images here. That means the images can have pixel values in the range of [0, 255] and the values have to be integers.
Let's do a binary thresholding operation. It creates a black and white masked image. The black regions have value 0 and the white regions 255
_, mask = cv.threshold(im_gray, thresh=180, maxval=255, type=cv.THRESH_BINARY)
im_thresh_gray = cv.bitwise_and(im_gray, mask)
The mask can be seen below on the left. The image on its right is the result of applying bitwise_and operation between the gray image and the mask. What happened is, the spatial locations where the mask had a pixel value zero (black), became pixel value zero in the result image. The locations where the mask had pixel value 255 (white), the resulting image retained its original gray value.
To apply this mask to our original color image, we need to convert the mask into a 3 channel image as the original color image is a 3 channel image.
mask3 = cv.cvtColor(mask, cv.COLOR_GRAY2BGR) # 3 channel mask
Then, we can apply this 3 channel mask to our color image using the same bitwise_and function.
im_thresh_color = cv.bitwise_and(im_color, mask3)
mask3 from the code is the image below on the left, and im_thresh_color is on its right.
You can plot the results and see for yourself.
cv.imshow("original image", im_color)
cv.imshow("binary mask", mask)
cv.imshow("3 channel mask", mask3)
cv.imshow("im_thresh_gray", im_thresh_gray)
cv.imshow("im_thresh_color", im_thresh_color)
cv.waitKey(0)
The original image is lenacolor.png that I found here.
Answer given by Abid Rahman K is not completely correct. I also tried it and found very helpful but got stuck.
This is how I copy image with a given mask.
x, y = np.where(mask!=0)
pts = zip(x, y)
# Assuming dst and src are of same sizes
for pt in pts:
dst[pt] = src[pt]
This is a bit slow but gives correct results.
EDIT:
Pythonic way.
idx = (mask!=0)
dst[idx] = src[idx]
The other methods described assume a binary mask. If you want to use a real-valued single-channel grayscale image as a mask (e.g. from an alpha channel), you can expand it to three channels and then use it for interpolation:
assert len(mask.shape) == 2 and issubclass(mask.dtype.type, np.floating)
assert len(foreground_rgb.shape) == 3
assert len(background_rgb.shape) == 3
alpha3 = np.stack([mask]*3, axis=2)
blended = alpha3 * foreground_rgb + (1. - alpha3) * background_rgb
Note that mask needs to be in range 0..1 for the operation to succeed. It is also assumed that 1.0 encodes keeping the foreground only, while 0.0 means keeping only the background.
If the mask may have the shape (h, w, 1), this helps:
alpha3 = np.squeeze(np.stack([np.atleast_3d(mask)]*3, axis=2))
Here np.atleast_3d(mask) makes the mask (h, w, 1) if it is (h, w) and np.squeeze(...) reshapes the result from (h, w, 3, 1) to (h, w, 3).

Categories