Multiple numpy place calls yield odd results - python

I'm trying to create an 8-bit 1-channel mask used for use in some image operations. I have an image that has certain pixels filled with fuscia (255, 0, 255) in the original image which indicates that pixel should be used in masking.
My idea is to simply copy the original picture, then replace all the fuscia pixels with white, and all the non-fuscia pixels with black. I am using numpy.place to do this. It appears, however, to only really "apply" the last place operation.
For example, in the code below, I am trying to first set all the fuscia pixels to white, and then all the non-fuscia pixels to black. However, when I go and actually save the image out and look at it, only the non-fuscia pixels have been turned black.
mask = original.copy()
np.place(mask, mask == (255, 0, 255), (255, 255, 255))
np.place(mask, mask != (255, 0, 255), (0, 0, 0))
mask = mask.reshape((h, w, 3))
mask = cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)
original
mask
I expect the fuscia area to be white, but it isn't. It is the greyscale version of the fuscia color (112, 112, 112)
I'm fairly new to numpy, so I may even be barking up the wrong tree and there could be an easier way to do this. What am I doing wrong? Is there an easier way to do what I'm describing? Thanks!

Seems like you could use a boolean array as the mask. For example:
mask = np.any(original==[255, 0, 255], axis=-1)
Now you can do original[mask] to get only the magenta pixels, or orignal[~mask] to get the others.
You'll find you can't overwrite original but you can overwrite a copy:
newimg = original.copy()
newimg[mask] = [255, 255, 255]
newimg[~mask] = [0, 0, 0]
By the way, I think you're 'supposed' to use masked arrays for this sort of thing, but I never got to grips with those.

Related

How to color a grayscale image based on a mask?

I have two images: A grayscale image, and a binary mask with the same dimensions. How do I color the image on the mask, while the rest of the image remains grayscale?
Here's and example:
Expressing your grey image pixel values across 3-channels gives you a color image. The result would look the same but when you check the dimensions it would be 3.
gray_3_channel = cv2.merge((gray, gray, gray))
gray.shape
>>> (158, 99)
gray_3_channel.shape
>>> (158, 99, 3)
For every white (255) pixel in the mask, assign the color (255, 255, 0) in gray_3_channel:
gray_3_channel[mask==255]=(255, 255, 0)

How do I count the number of flowers (yellow) from the image in python

I'm a newbie in python . I have tried to extract the flower (maybe not too accurate) but I need to : (1). be able count how many features has been extracted. (2) Other objects in the image that appears yellowish but not the object I wanted. Thanks for helping
Below is my code at the moment
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread("female29.jpg")
image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
image2 = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
yellow = (204, 204, 0)
light_yellow = (255, 255, 204)
mask = cv2.inRange(image, yellow, light_yellow)
result = cv2.bitwise_and(image, image, mask=mask)
cv2.imshow("image", result)
cv2.waitKey(0)
Sadly there is no easy way to count the number of flowers present solely using opencv as what the above code does is only highlight or apply a mask to parts of image with the pixel values in the range you supplied.
yellow = (204, 204, 0)
light_yellow = (255, 255, 204)
you can try and change different values for your ranges to more accurately mask the flower
probably use an online HSV color picker to adjust the values (keep in mind you will have to scale the values from 0-100 to 0-255).
You can only try adjusting these values in order to prevent other Other objects in the image that appears yellowish but at the end of the day you're just applying a color filter, so everything that is yellow will show up and there is no way to stop that unless you use higher methods like Machine Learning to recognize flowers

Numpy np.any range or threshold

I am using python, OpenCV and Numpy. My goal is to find all white pixel and turn it red and turn everything else off or white. My code:
import numpy as np
import cv2
import matplotlib.pyplot as plt
# Read mask
image = cv2.imread("path to my image")
any_white = np.any(image == [255,255,255], axis = -1)
image[any_white]=[255,0,0]
plt.imshow(image)
plt.show()
cv2.imwrite('result.png',image)
Problem 1: Targetting any [255,255,255] doesn't find all, whiteist, I starting finding any [244,244,244], [243,243,243] and so on. Is there a way to set a range of white, maybe from [255,255,255] to [230,230,230]?
Problem 2: clearly, with plt.imshow(image) and plt.show() within python, the result shows red, but when i used cv2.imwrite('result.png',image) to save, it's blue. See result image.
Problem 1:
You can create a mask and set the red channel to False so that you keep the value at 255 if you want to target only the white pixels
mask_bg = (image == [255, 255, 255])
mask_bg[:, :, 0] = False # set red channel mask to false (leave 255 value)
image[mask_bg] = 0 # set all white pixels to [255, 0, 0]
If you want to find all values in a range you can use cv2.inRange:
mask = cv2.inRange(image, (230, 230, 230), (255, 255,255))
Problem 2:
OpenCV uses BGR as default instead of RGB, you can convert from BGR to RGB with:
new_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
cv2.imshow('BGR Image', new_image )
Keep in mind that if you open an image with OpenCV it will be BGR, so convert it before manipulating the channels.
Problem 1:
The pixels you are planning to target may not have the exact value of (255, 255, 255). Hence it is better to binarize the image by setting a range of pixel values. You can find the exact range by creating Trackbars and tuning them manually. You can find more about implementing Trackbars in OpenCV here.
Problem 2:
This happens because OpenCV uses BGR or (Blue, Green, Red) colorspace by default. You can change the colorspace into RGB or (Red, Green, Blue) by using cv2.cvtColor(image, cv2.COLOR_BGR2RGB) before saving.

How to create overlapping semitransparent shapes on semitransparent background with PIL

I'm trying to create image with semitransparent shapes drawn on transparent background. For some reason instead of staying transparent, the shapes are completely covering those beneath them. My code:
from PIL import Image, ImageDraw
img = Image.new("RGBA", (256, 256), (255,0,0,127))
drawing = ImageDraw.Draw(img, "RGBA")
drawing.ellipse((127-79, 127 - 63, 127 + 47, 127 + 63), fill=(0, 255, 0, 63), outline=(0, 255, 0, 255))
drawing.ellipse((127-47, 127 - 63, 127 + 79, 127 + 63), fill=(0, 0, 255, 63), outline=(0, 0, 255, 255))
img.save("foo.png", "png")
I would expect the result to look something like (except for background not being transparent):but it looks like:
When I try to save it as GIF with img.save("foo.gif", "gif"), result is even worse. Circles are solid, no difference between outline and fill.
As I mentioned in a comment, ImageDraw.Draw doesn't do blending—whatever is drawn replaces whatever pixels that were there previously. To get the effect you want requires drawing things in a two-step process. The ellipse must first be drawn on a blank transparent background, and then that must be alpha-composited with current image (bg_img) to preserve transparency.
In the code below this has been implementing in re-usable function:
from PIL import Image, ImageDraw
def draw_transp_ellipse(img, xy, **kwargs):
""" Draws an ellipse inside the given bounding box onto given image.
Supports transparent colors
"""
transp = Image.new('RGBA', img.size, (0,0,0,0)) # Temp drawing image.
draw = ImageDraw.Draw(transp, "RGBA")
draw.ellipse(xy, **kwargs)
# Alpha composite two images together and replace first with result.
img.paste(Image.alpha_composite(img, transp))
bg_img = Image.new("RGBA", (256, 256), (255, 0, 0, 127)) # Semitransparent background.
draw_transp_ellipse(bg_img, (127-79, 127-63, 127+47, 127+63),
fill=(0, 255, 0, 63), outline=(0, 255, 0, 255))
draw_transp_ellipse(bg_img, (127-47, 127-63, 127+79, 127+63),
fill=(0, 0, 255, 63), outline=(0, 0, 255, 255))
bg_img.save("foo.png")
This is the image it created viewed in my image file editor app which renders semi-transparent portions of images with a checker-board pattern. As you can see the opaque outlines are the only part that isn't.
All 4th dimensions-transparency must be half. Also maybe problem is with drawing. Make mask for each ellipse and then sum up images. Last option- check pil function for bending images. That will propably be most certain and easiest solution.
The function for blending is:
PIL.Image. blend (im1, im2, alpha)

Calculating the number of blue pixels in a picture

I am new to openCV and Python and a have a question concerning it. I am trying to find the amount of blue pixels of a picture so i can use them as a threshold in order to compare other pictures with it. I have tried looking through the documentation but i couldn't find anything helpful yet.
Can anyone give a hint or some help?
BLUE_MAX = np.array([0, 0, 200], np.uint8)
BLUE_MIN = np.array([50, 50, 255], np.uint8)
dst = cv.inRange(img, BLUE_VALUE_MIN, BLUE_VALUE_MAX)
no_blue = cv.countNonZero(dst)
print('The number of blue pixels is: ' + str(no_blue))
-So based on your recommendation I built the following function but all I get when I run it is a blank picture.
For counting blue pixel in a RGB image you can simply do the following
Inrange the source image to filter out blue component to a binary image.
Count non zero pixel in the binary image using the function countNonZero.
You can refer below C++ code for how to do it
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat src,dst;
src = imread("rgb1.jpg",1);
inRange(src, Scalar(200,0,0), Scalar(255,50,50), dst); //In range with approximate blue range
cout<<"No of blue pixels---->"<<countNonZero(dst)<<endl;
imshow("src",src);
imshow("out",out);
waitKey(0);
return 0;
}
Edit:-
Here is the working python code
import cv2
import numpy as np
img = cv2.imread("bgr.png")
BLUE_MIN = np.array([0, 0, 200], np.uint8)
BLUE_MAX = np.array([50, 50, 255], np.uint8)
dst = cv2.inRange(img, BLUE_MIN, BLUE_MAX)
no_blue = cv2.countNonZero(dst)
print('The number of blue pixels is: ' + str(no_blue))
cv2.namedWindow("opencv")
cv2.imshow("opencv",img)
cv2.waitKey(0)
Hope this is what you looking for.....
Edit2:-
As # kigurai commented below OpenCV consider image in BGR order and I gave wrong order for BLUE_MIN and BLUE_MAX array.
So in the above code the lines
BLUE_MIN = np.array([0, 0, 200], np.uint8)
BLUE_MAX = np.array([50, 50, 255], np.uint8)
should changed to
BLUE_MIN = np.array([200, 0, 0], np.uint8) // minimum value of blue pixel in BGR order
BLUE_MAX = np.array([255, 50, 50], np.uint8)// maximum value of blue pixel in BGR order
If you are looking for blue pixels in a photographed image, I recommend converting to HSV colour space first and then look for the color range for blue. This way, you can ignore the brightness component.
See this question for colour ranges in HSV color space.

Categories