I have an image which looks like this:
I want to convert the background to white and all the other pixels to black so that my image look likes this:
Let's say the original image is img and the above result is mask. When I try this to get the mask from the original image, things don't work as expected. I did this:
mask = np.ones_like(img)*255
mask[img > 0] = 0
Ideally I should get the expected result but this is what I am getting instead.
Also, I have another image which looks like this:
I want to paste the expected mask on this final sunset image. How can I do that using numpy/scipy/PIL/skimage?
Since we are looking to get anything that's not black in img to be set as zero in mask, just look for ANY along the three channels (last axis) and use that boolean array for masking into mask -
mask[(img>0).any(-1)] = 0
Output for given sample #1 -
To mix it with the sunset image img2 -
from scipy.misc import imresize
mask_resized = imresize(mask, size=img2.shape)
out = (mask_resized==255)*img2
Output -
Related
So I have this python script that detects and print a range of HSV color in an image but i want to add another functionality to it.
It want it to be able to print the percentage of that color.
My Script:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('C:/Users/Vishu Rana/Documents/PY/test_cases/image.jpg')
grid_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(20,8))
plt.imshow(grid_RGB) # Printing the original picture after converting to RGB
grid_HSV = cv2.cvtColor(grid_RGB, cv2.COLOR_RGB2HSV) # Converting to HSV
lower_green = np.array([25,52,72])
upper_green = np.array([102,255,255])
mask= cv2.inRange(grid_HSV, lower_green, upper_green)
res = cv2.bitwise_and(img, img, mask=mask) # Generating image with the green part
print("Green Part of Image")
plt.figure(figsize=(20,8))
plt.imshow(res)
What do i need to add in this code to make it also print the percentage of green color.
OpenCV arrays create a mask that uses the value 255. A simple way to get the percentage of green is simply implement the following code after you generate the mask.
green_perc = (mask>0).mean()
A more thorough explanation was asked about why this works. When OpenCV creates a mask it will create a 2 dimensional array with values of 0s and values of 255. In the context of this question the values of 255 are the parts of the mask that indicate the picture is green.
The reason that (mask>0).mean() works is because we only have values of 255 and 0. Mask > 0 will create a boolean array of True/False for every value in our mask.
The value True will indicate that the part of the array is green and the value of False will indicate it is not green. Taking the mean of this boolean array will give us the percentage of the array that is green. (when taking the mean of a boolean array True will take the value of 1 and False will take the value of 0).
Another way to get the same result is to implement code like this.
green_perc = (mask==255).mean()
A comment above also mentions the solution of np.sum(mask)/np.size(mask). This does not work right because OpenCV uses the value 255. You could tweak this to get the same percentage by dividing this value by 255.
green_perc = (np.sum(mask) / np.size(mask))/255
I wrote this code to switch the red and blue values in the RGB array from a given image:
from PIL import Image
import numpy as np
image = Image.open("image.jpg")
RGBarr = np.asarray(image)
newRGB = np.full_like(RGBarr, 1)
red = RGBarr[..., 0]
green = RGBarr[...,1]
blue = RGBarr[..., 2]
newRGB[..., 0] = blue
newRGB[..., 1] = green
newRGB[..., 2] = red
inv_image = Image.fromarray(newRGB, 'RGB')
inv_image.save('inv_image.png')
inv_image.show()
I tried it with multiple images, and it works almost every time. However, in some cases I get the following error:
raise ValueError("not enough image data")
ValueError: not enough image data
That can be fixed if I do not specify the mode in Image.fromarray(obj, mode), but even doing that I am not sure if the result I obtain is the "correct" one.
Is there a way to determine what mode should be used for a certain image?
I hope this is not a dumb question, but I am sort of new in this image processing business.
The error occurs, when you try to read images which are not RGB like grayscale images or RGBA images. To keep the rest of your code valid, the easiest way would be to enforce RGB input by using:
image = Image.open("image.jpg").convert('RGB')
Then, possible grayscale or RGBA images are converted to RGB, and can be processed as regular RGB images.
As you found out yourself,
inv_image = Image.fromarray(newRGB)
also works, but the processing from the rest of your code then isn't correct anymore (no proper slicing of the desired dimensions/axes). That would require further work on your code to also respect grayscale or RGBA images.
Hope that helps!
EDIT: To incorporate furas' idea to get rid of NumPy, here's a PIL only way of swapping the channels. Notice: You still need the enforced RGB input.
from PIL import Image
image = Image.open('image.jpg').convert('RGB')
r, g, b = image.split()
inv_image = Image.merge('RGB', (b, g, r))
inv_image.save('inv_image.png')
inv_image.show()
If you want to re-order RGB channels to BGR with Numpy, it is much simpler to do this:
BGR = RGB[...,::-1]
which just addresses the last index (i.e. the channels) in reverse. It has the benefit of being O(1) which means it takes the same amount of time regardless of the size of the array. On my Mac, it takes 180ns to do BGR->RGB with 10x10 image and just the same with a 10,000x10,000 image.
In general, you may want some other ordering rather than straight reversal, so if you want BGR->BRG, you can do:
BRG = BGR[...,(0,2,1)]
Or, if you want to make a 3-channel greyscale image by repeating the Green channel three times (because the green is usually the least noisy - see Wikipedia Bayer array article), you can simply do this:
RGBgrey = BGR[...,(1,1,1)]
If you want to get rid of Numpy, you can do it straight in PIL/Pillow using a matrix multiplication:
# Open image
im = Image.open('image.jpg')
# Define matrix to re-order RGB->BGR
Matrix = ( 0, 0, 1, 0,
0, 1, 0, 0,
1, 0, 0, 0)
# BGR -> RGB
BGR = im.convert("RGB", Matrix)
You can understand the matrix like this:
newR = 0*oldR + 0*oldG + 1*oldB + 0 offset
newG = 0*oldR + 1*oldG + 0*oldB + 0 offset
newB = 1*oldR + 0*oldG + 0*oldB + 0 offset
Input
Result
I am trying to make my code more robust compared to my first revision. The goal is to generate a final single image by comparing image A and image B to get image C. Currently I am working to show differences in images composed of black lines. In this case, that would be image A and B. I have a working method with imaging resizing and the pre-processing done (resizing, noise reduction, etc). The code I developed to show the differences (image C) is shown below:
np_image_A = np.array(image_A)
np_image_B = np.array(image_B)
# Set the green and red channels respectively to 0. Leaves a blue image
np_image_A[:, :, 1] = 0
np_image_A[:, :, 2] = 0
# Set the blue channels to 0.
np_image_B[:, :, 0] = 0
# Add the np images after color modification
overlay_image = cv2.add(np_image_A, np_image_B)
I currently don't feel that is is robust enough and may lead to some issues down the line. I want to use a method that shows the image differences between image A and B in a single image. And image A be assigned one color for differences and image B be assigned another color (such as blue and red, and black represents areas that are the same). This is highlighted in the image below:
To remedy this, I received some help from StackOverflow and now have a method that uses masking and merging in OpenCV. The issue that I have found is that only additive changes are shown, and if an item is removed, it is not show in the difference image.
Here is the updated code that gets me part of the way to the solution that I am seeking.The issue with this code is that it produces what is found in image D and not image C. I tried to essentially run this block of code twice, switching img = imageA and imageB, but the output is mangled for some reason.
# load image A as color image
img = cv2.imread('1a.png')
# load A and B as grayscale
imgA = cv2.imread('1a.png',0)
imgB = cv2.imread('1b.png',0)
# invert grayscale images for subtraction
imgA_inv = cv2.bitwise_not(imgA)
imgB_inv = cv2.bitwise_not(imgB)
# subtract the original (A) for the new version (B)
diff = cv2.subtract(imgB_inv, imgA_inv)
# split color image A into blue,green,red color channels
b,g,r = cv2.split(img)
# merge channels back into image, subtracting the diff from
# the blue and green channels, leaving the shape of diff red
res = cv2.merge((b-diff,g-diff,r))
# display result
cv2.imshow('Result',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
The result that I am looking for is image C, but currently I can only achieve image D with the revised code.
Edit: Here are the test images A and B for use.
You're almost there, but you need to create two separate diffs. One diff represents the black pixels that are in A but not in B, and the other diff represents the black pixels that are in B but not in A.
Result:
import cv2
import numpy as np
# load A and B as grayscale
imgA = cv2.imread('1a.png',0)
imgB = cv2.imread('1b.png',0)
# invert grayscale images for subtraction
imgA_inv = cv2.bitwise_not(imgA)
imgB_inv = cv2.bitwise_not(imgB)
# create two diffs, A - B and B - A
diff1 = cv2.subtract(imgB_inv, imgA_inv)
diff2 = cv2.subtract(imgA_inv, imgB_inv)
# create a combined image of the two inverted
combined = cv2.add(imgA_inv, imgB_inv)
combined_inv = cv2.bitwise_not(combined)
# convert the combined image back to rbg,
# so that we can modify individual color channels
combined_rgb = cv2.cvtColor(combined_inv, cv2.COLOR_GRAY2RGB)
# split combined image into blue,green,red color channels
b,g,r = cv2.split(combined_rgb)
# merge channels back into image, adding the first diff to
# the red channel and the second diff to the blue channel
res = cv2.merge((b+diff2,g,r+diff1))
# display result
cv2.imshow('Result',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
I have a picture like this and i want to remove the background by adding a layer on top the image just like how photoshop layout works.
Original Picture
Mask/Layer
Final desired output
I am trying to do this iwth opencv's addweighed function but i am not able to get the desired output
im_overlay = cv2.imread('%s/%s.png'%(_src,camera_name.split(".")[0]))
img = cv2.addWeighted(im, 1, im_overlay, 0.0, 0)
showImage(img)
The format of the mask is strange for OpenCv to read on my system (using opencv 3.4.2). I was able to read it with cv2.IMREAD_UNCHANGED but it show me that your mask has 4 channels (I was expecting only one channel). The code below produce opposite or what you would expect:
img = cv2.imread(r"C:\Users\...\Desktop\\W1kle.jpg")
mask = cv2.imread(r"C:\Users\...\Desktop\LZdyB.png",cv2.IMREAD_UNCHANGED)
mask = mask[:,:,3]
res = cv2.bitwise_and(img,img,mask=mask)
cv2.imshow("image",res)
cv2.waitKey(0)
Hope this helps you :)
I am really new to opencv and a beginner to python.
I have this image:
I want to somehow apply proper thresholding to keep nothing but the 6 digits.
The bigger picture is that I intend to try to perform manual OCR to the image for each digit separately, using the k-nearest neighbours algorithm on a per digit level (kNearest.findNearest)
The problem is that I cannot clean up the digits sufficiently, especially the '7' digit which has this blue-ish watermark passing through it.
The steps I have tried so far are the following:
I am reading the image from disk
# IMREAD_UNCHANGED is -1
image = cv2.imread(sys.argv[1], cv2.IMREAD_UNCHANGED)
Then I'm keeping only the blue channel to get rid of the blue watermark around digit '7', effectively converting it to a single channel image
image = image[:,:,0]
# openned with -1 which means as is,
# so the blue channel is the first in BGR
Then I'm multiplying it a bit to increase contrast between the digits and the background:
image = cv2.multiply(image, 1.5)
Finally I perform Binary+Otsu thresholding:
_,thressed1 = cv2.threshold(image,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
As you can see the end result is pretty good except for the digit '7' which has kept a lot of noise.
How to improve the end result? Please supply the image example result where possible, it is better to understand than just code snippets alone.
You can try to medianBlur the gray(blur) image with different kernels(such as 3, 51), divide the blured results, and threshold it. Something like this:
#!/usr/bin/python3
# 2018/09/23 17:29 (CST)
# (中秋节快乐)
# (Happy Mid-Autumn Festival)
import cv2
import numpy as np
fname = "color.png"
bgray = cv2.imread(fname)[...,0]
blured1 = cv2.medianBlur(bgray,3)
blured2 = cv2.medianBlur(bgray,51)
divided = np.ma.divide(blured1, blured2).data
normed = np.uint8(255*divided/divided.max())
th, threshed = cv2.threshold(normed, 100, 255, cv2.THRESH_OTSU)
dst = np.vstack((bgray, blured1, blured2, normed, threshed))
cv2.imwrite("dst.png", dst)
The result:
Why not just keep values in the image that are above a certain threshold?
Like this:
import cv2
import numpy as np
img = cv2.imread("./a.png")[:,:,0] # the last readable image
new_img = []
for line in img:
new_img.append(np.array(list(map(lambda x: 0 if x < 100 else 255, line))))
new_img = np.array(list(map(lambda x: np.array(x), new_img)))
cv2.imwrite("./b.png", new_img)
Looks great:
You could probably play with the threshold even more and get better results.
It doesn't seem easy to completely remove the annoying stamp.
What you can do is flattening the background intensity by
computing a lowpass image (Gaussian filter, morphological closing); the filter size should be a little larger than the character size;
dividing the original image by the lowpass image.
Then you can use Otsu.
As you see, the result isn't perfect.
I tried a slightly different approach then Yves on the blue channel:
Apply median filter (r=2):
Use Edge detection (e.g. Sobel operator):
Automatic thresholding (Otsu)
Closing of the image
This approach seems to make the output a little less noisy. However, one has to address the holes in the numbers. This can be done by detecting black contours which are completely surrounded by white pixels and simply filling them with white.