Alpha masking a non square region python cv2 - python

I have a 2d numpy array that has 0's where there is not an object, and 1's where there is an object.
matrix.shape = (500, 425)
I want to create a numpy array mask of shape (500, 425, 3) such that:
mask = np.zeros((500, 425, 3))
if matrix[x][y] == 0:
mask[x][y] = np.array([0, 0, 0]) # Black pixel if no object
else:
mask[x][y] = np.array([0, 255, 0]) # Green pixel if object
So that I have green pixels where the object are, black pixels everywhere else. How do I create this mask? Will this work correctly, such that if I use cv2.addWeighted to the image and the mask, the object will have a transparent green mask over it?

You're describing an "over" or "blend" image compositing operation. You can combine the images directly using your mask image.The general formula for combining two images in this way is:
A*alpha + B*(1-alpha)
Where A is the image being placed on top of image B. Alpha can be any value between black and white. Gray alpha values will make A appear transparent. It is usually easier to convert to a float image because the math is much easier when the values are between 0 and 1.
If you have image A (your source image) and image B (a green image) and your mask (matrix). You can overlay image B on top of image A using:
outimg[x][y] = (B[x][y] * matrix[x][y]) + (A[x][y] * (1-matrix[x][y]))
or if you want transparency:
#50% transparency
t = 0.5
outimg[x][y] = (B[x][y] * (matrix[x][y]*t)) + (A[x][y] * (1-(matrix[x][y]*t)))
If you want to put image A on top of image B you can just reverse the terms in the expression.
Here is an example of compositing image A (a flat green image) on top of image B (the source image) with a mask and transparency:
source image:
mask image:
import numpy as np
import cv2
i = cv2.imread('lena.bmp')
#convert to floating point
img = np.array(i, dtype=np.float)
img /= 255.0
cv2.imshow('img',img)
cv2.waitKey(0)
j = cv2.imread('lena_mask.bmp')
#convert to floating point
mask = np.array(j, dtype=np.float)
mask /= 255.0
#set transparency to 25%
transparency = .25
mask*=transparency
cv2.imshow('img',mask)
cv2.waitKey(0)
#make a green overlay
green = np.ones(img.shape, dtype=np.float)*(0,1,0)
#green over original image
out = green*mask + img*(1.0-mask)
cv2.imshow('img',out)
cv2.waitKey(0)
cv2.destroyAllWindows()
Output image:

One easy way would be with broadcasting after extending matrix to 3D and simply multiplying with the green colour triplet, like so -
matrix[...,None]*[0,255,0]
Sample run -
In [35]: matrix
Out[35]:
array([[1, 0, 0, 0],
[1, 0, 0, 1],
[0, 0, 1, 0]])
In [36]: matrix[...,None]*[0,255,0]
Out[36]:
array([[[ 0, 255, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0]],
[[ 0, 255, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 255, 0]],
[[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 255, 0],
[ 0, 0, 0]]])
The second column signifying the green colour.
Please note that this is not alpha masking, which generally involves the fourth channel, but is a simple RGB masking.
Another approach with zeros based initialization and might be better on performance -
m,n = matrix.shape
out = np.zeros((m,n,3),dtype=np.uint8)
out[matrix==1,1] = 255 # green channel accessed with the last index being 1

Related

OpenCV projectPoints results in Z axis being too long

I'm trying to plot a rotated xyz axis using OpenCV projectPoints function. When testing rotation about the X and Y axis, I noticed that the Z axis is much longer than the X and Y axis when they should be the same length, but I am unsure why. Any help would be greatly appreciated!
Here are some images I generated:
Here is my code:
import numpy as np
import cv2
import sys
def rotByXAxis(angle):
return np.array([
[1, 0, 0],
[0, np.cos(angle), -np.sin(angle)],
[0, np.sin(angle), np.cos(angle)],
], dtype=np.float32)
def rotByYAxis(angle):
return np.array([
[ np.cos(angle), 0, np.sin(angle)],
[ 0, 1, 0],
[-np.sin(angle), 0, np.cos(angle)],
], dtype=np.float32)
def rotByZAxis(angle):
return np.array([
[ np.cos(angle), np.sin(angle), 0],
[-np.sin(angle), np.cos(angle), 0],
[ 0, 0, 1],
], dtype=np.float32)
def createCanvas(self, height, width):
blank_image = np.zeros((height, width, 3), np.uint8)
blank_image[:, :] = (255, 255, 255)
return blank_image
def draw3DAxis(self, image, rvec, tvec, cameraMatrix, scale=1, dist=None):
"""
Draw a 6d of axis (XYZ -> RGB) in the given rotation and translation
:param image - rgb numpy array
:rvec - euler rotations, numpy array of length 3,
use cv2.Rodrigues(R)[0] to convert from rotation matrix
:tvec - 3d translation vector, in meters (dtype must be float)
:cameraMatrix - intrinsic calibration matrix , 3x3
:scale - factor to control the axis lengths
:dist - optional distortion coefficients, numpy array of length 4. If None distortion is ignored.
"""
image = image.astype(np.float32)
dist = np.zeros(4, dtype=float) if dist is None else dist
if rvec.shape == (3, 3):
rvec, _ = cv2.Rodrigues(rvec)
points = scale * np.float32([
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[0, 0, 0]
]).reshape(-1, 3)
axis_points, _ = cv2.projectPoints(points, rvec, tvec, cameraMatrix, dist)
print(axis_points)
image = cv2.arrowedLine(
image,
tuple(int(e) for e in axis_points[3].ravel()),
tuple(int(e) for e in axis_points[0].ravel()),
(255, 0, 0),
3,
tipLength=0.01 * scale,
)
image = cv2.arrowedLine(
image,
tuple(int(e) for e in axis_points[3].ravel()),
tuple(int(e) for e in axis_points[1].ravel()),
(0, 255, 0),
3,
tipLength=0.01 * scale,
)
image = cv2.arrowedLine(
image,
tuple(int(e) for e in axis_points[3].ravel()),
tuple(int(e) for e in axis_points[2].ravel()),
(0, 0, 255),
3,
tipLength=0.01 * scale,
)
return image
if __name__ == "__main__":
height = 300
width = 400
image = createCanvas(height, width)
rvec = rotByZAxis(-pi/2)
cameraMatrix = np.array([
[1.0, 0, width/2],
[ 0, 1.0, height/2],
[ 0, 0, 1.0 ]], dtype=np.float32)
image = draw3DAxis(
image,
rvec=rvec,
tvec=np.zeros(3, dtype=float),
cameraMatrix=cameraMatrix,
scale=20,
)
cv2.imshow("output", image)
key = cv2.waitKey(0)
if key:
sys.exit(1)

Setting the alpha channel of an image, based on the pixel values in another image

I have two images. In one image all non-alpha channel pixels are equal to 0, and I'd like the alpha channel values to equal 255 where in the other image which is of equal size, the pixels are anything but 0. In this attempt, I'm attempting to create a 4 channel np array based off of the original image, and then use np.argwhere to find where the pixel valeus are non-zero, and then in the new np array, set the alpha channel value based on that.
For example, for each pixel in my input image with values [255, 255, 255], I'd like the corresponding pixel in my new image to be [0, 0, 0, 255]. For each pixel in my input image with values [0, 0, 0], I'd like the corresponding pixel in my new image to be [0, 0, 0, 0].
mask_file = cv.imread(r'PlateMask_0001.png', cv.IMREAD_UNCHANGED)
scale_factor = 0.125
w = int(mask_file.shape[1] * scale_factor)
h = int(mask_file.shape[0] * scale_factor)
scaled = cv.resize(mask_file, (w, h))
coords = np.argwhere(scaled > 0)
new_object = np.zeros((120, 160, 4))
new_object[coords, :] = 255
cv.imshow('Mask', mask)
cv.imshow('Scaled', new_object)
cv.waitKey(0)
cv.destroyAllWindows()
This is my first question on Stack so please feel free to suggest improvements on question formatting, etc. Thank you.
Consider img1 to be your original image and img2 to be the image where alpha channel needs to be modified.
In the following, the alpha channel of img2 contains value 255 in the coordinate where img1 has (255, 255, 255):
img2[:,:,3][img1 == (255, 255, 255)] = 255
Likewise for value 0:
img2[:,:,3][img1 == (0, 0, 0)] = 0

How to delete objects (cells) touching the image boundaries?

I have an image of cells which I have thresholded and also detected the cells (using cv2).
I would like to create an array with values True or False to show whether each component touches the boundaries of the image (True) or not (False).
import cv2 as cv
# Read the image you want connected components of, IN BLACK AND WHITE
img = cv.imread('../images/37983_ERSyto/cellpaintingfollowup-reimage_a01_s1_w26ae36209-938b-45ef-b166-3aba3af125df.tif', cv.IMREAD_GRAYSCALE)
seed_pt = (100, 800) #point in the background
fill_color = 0
mask = np.zeros_like(img)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (6, 5))
for th in range(7,70):
#creates a copy of the mask:
prev_mask = mask.copy()
#thresholded image:
mask = cv.threshold(img, th, 22331, cv.THRESH_BINARY)[1]
#FloodFill: fill a connected component starting from the seed point with the specified color.
mask = cv.floodFill(mask, None, seed_pt, fill_color)[1]
#cv.bitwise: calculates the per-element bit-wise disjunction of two arrays or an array and a scalar. Superposition of thresholded images
mask = cv.bitwise_or(mask, prev_mask)
#clean speckles
mask = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel)
#compute the connected components labeled image of boolean image and also produce a statistics output for each label
connectivity = 8 #You need to choose 4 or 8 for connectivity type.
#OBTAIN FEATURE OF THE AREA IN PIXELS OF THE CELLS
stats = cv.connectedComponentsWithStats(mask, connectivity, cv.CV_32S)[2]
label_area = stats[1:, cv.CC_STAT_AREA] #we dont include the first element because it represents the area of the background
#OBTAIN FEATURES OF THE CENTROID POSITION
centroids = cv.connectedComponentsWithStats(mask, connectivity, cv.CV_32S)[3]
label_centroids_x = centroids[1:, 0] #dont include the first element because it represents the background
label_centroids_y = centroids[1:,1]
#HIGHT: The vertical size of the bounding box.
label_hight = stats[1:, cv.CC_STAT_HEIGHT]
#WIDTH: The horizontal size of the bounding box.
label_width = stats[1:, cv.CC_STAT_WIDTH]
#TOUCHING IMAGE BOUNDARIES: is the component touching the boundaries of the matrix/image?--> True/False
label_bounary = #boolean array
I first thought about searching for the contour of every component and defining some restriction, but I have troubles understanding how the labels of every component are stored and therefore, I could not select the desired components.
Here is the image:
Thank you very much in advance.
Using your code (thanks for commenting), I got this mask. It's possible it's not the same since .jpg compression can mess with an image (it's not a lossless compression scheme)
#fmw42 is exactly right, he commented before I could finish my code
import cv2 as cv
import numpy as np
# Read the image you want connected components of, IN BLACK AND WHITE
img = cv.imread('cells.jpg', cv.IMREAD_GRAYSCALE)
seed_pt = (100, 800) #point in the background
fill_color = 0
mask = np.zeros_like(img)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (6, 5))
for th in range(7,70):
#creates a copy of the mask:
prev_mask = mask.copy()
#thresholded image:
mask = cv.threshold(img, th, 22331, cv.THRESH_BINARY)[1]
#FloodFill: fill a connected component starting from the seed point with the specified color.
mask = cv.floodFill(mask, None, seed_pt, fill_color)[1]
#cv.bitwise: calculates the per-element bit-wise disjunction of two arrays or an array and a scalar. Superposition of thresholded images
mask = cv.bitwise_or(mask, prev_mask)
#clean speckles
mask = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel)
# show mask
cv.imshow("Mask", mask);
cv.waitKey(0);
# contours OpenCV 3.4, if you're using OpenCV 2 or 4, it returns (contours, _)
_, contours, _ = cv.findContours(mask, cv.RETR_TREE, cv.CHAIN_APPROX_NONE);
# get bounds and check if they're touching edge
height, width = mask.shape[:2];
touching_edge = []; # boolean array, index matches the contours list
for con in contours:
# get bounds
x, y, w, h = cv.boundingRect(con);
# check if touching edge
on_edge = False;
if x <= 0 or (x + w) >= (width - 1):
on_edge = True;
if y <= 0 or (y + h) >= (height - 1):
on_edge = True;
# add to list
touching_edge.append(on_edge);
# mark the contours on the edge
colored = cv.cvtColor(mask, cv.COLOR_GRAY2BGR);
for index in range(len(contours)):
if touching_edge[index]:
# drawContours(image, contour_list, index, color, thickness) # -1 is filled
cv.drawContours(colored, contours, index, (50,50,200), -1);
# show
cv.imshow("Colored", colored);
cv.waitKey(0);
If you're open to using scikit-image, you can try clear_border:
>>> import numpy as np
>>> from skimage.segmentation import clear_border
>>> labels = np.array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
... [1, 1, 0, 0, 1, 0, 0, 1, 0],
... [1, 1, 0, 1, 0, 1, 0, 0, 0],
... [0, 0, 0, 1, 1, 1, 1, 0, 0],
... [0, 1, 1, 1, 1, 1, 1, 1, 0],
... [0, 0, 0, 0, 0, 0, 0, 0, 0]])
>>> clear_border(labels)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]])

Python Pillow's Image.Split is not returning RBG, but images are all in a grayscale, what would cause this?

I am working on a course about Pillow. There is no information on how to do the assignment, so I am lost.
Reading the Pillow documentation is not helpful, so I am unsure why an image converted to RBG is not being split into RBG, but into grayscale.
help(Image.Image.split)
Looking at Python For Geeks.
they are also showing gray images instead of red, blue, and green images.
If the .split() is supposed to split it into the RBG tuple, why are the images only being shown in gray?
#Code snippet from Python for Geeks
im1 = Image.Image.split(image)
im1[0].show()
display(im1[0])
display(im1[1])
display(im1[2])
When you split a 3-channel RGB image, it becomes 3 separate, single-channel images. Single-channel images are generally considered greyscale. So you get 3 greyscale images.
Start instead with:
from PIL import Image
R, G, B = Image.split(...)
Now, if you want the Red channel to be shown in red, you'll need to make it back into a colour image, by creating an empty (zeroed out) Green and Blue channel, and merging back to colour:
empty = Image.new("L", SIZE, "black")
RedChannelInRed = Image.merge("RGB", (R, empty, empty))
GreenChannelInGreen = Image.merge("RGB", (empty, G, empty))
BlueChannelInBlue = Image.merge("RGB", (empty, empty, B))
Alternatively, rather than pulling the image apart into its constituent channels, and then re-assembling it, you could apply a colour matrix to convert the image "in-situ" like this:
#!/usr/bin/env python3
from PIL import Image
# Open image
im = Image.open('image.jpg')
# Define color matrix to retain red channel unchanged and zero the green and blue channels
# This says:
# New red = 1*old red + 0*old green + 0*old blue + 0offset
# New green = 0*old red + 0*old green + 0*old blue + 0offset
# New blue = 0*old red + 0*old green + 0*old blue + 0offset
RedMatrix = ( 1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0)
Red = im.convert('RGB', RedMatrix)
Red.save('result-R.jpg')
# Do the same thing for Green channel
GreenMatrix = ( 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)
Green = im.convert('RGB', GreenMatrix)
Green.save('result-G.jpg')
# Do the same thing for Blue channel
BlueMatrix = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0)
Blue = im.convert('RGB', BlueMatrix)
Blue.save('result-B.jpg')

Map scalars to arrays based on values : Image Processing 2D to 3D - NumPy / Python

Given a Numpy matrix of shape (height, width), I am looking for the fastest way to create another Numpy matrix of shape (height, width, 4) where 4 represents RGBA values. I would like to do this value-based; so, for all values of 0 in the first matrix I would like to have a value of [255, 255, 255, 0] in the second matrix at the same location.
I would like to do this with NumPy without needing to slowly iterate like below:
for i in range(0, height):
for j in range(0, width):
if image[i][j] = 0:
new_image[i][j] = [255, 255, 255, 0]
elif image[i][j] = 1:
new_image[i][j] = [0, 255, 0, 0.5]
As you can see, I am creating a matrix where the value 0 becomes transparent white, and 1 becomes green with an alpha of 0.5; are there faster NumPy solutions?
I am guessing numpy.where should greatly help speed up the process, but I haven't yet figured out the proper implementation for multiple and many value translations.
For a cleaner solutiuon, especially when working with multiple labels, we could make use of np.searchsorted to trace back the values for the mapping, like so -
# Edit to include more labels and values here
label_ar = np.array([0,1]) # sorted label array
val_ar = np.array([[255, 255, 255, 0],[0, 255, 0, 0.5]])
# Get output array
out = val_ar[np.searchsorted(label_ar, image)]
Note that this assumes that all unique labels from image are in label_ar.
So, now let's say we have two more labels 2 and 3 in image, something like this -
for i in range(0, height):
for j in range(0, width):
if image[i,j] == 0:
new_image[i,j] = [255, 255, 255, 0]
elif image[i,j] == 1:
new_image[i,j] = [0, 255, 0, 0.5]
elif image[i,j] == 2:
new_image[i,j] = [0, 255, 255, 0.5]
elif image[i,j] == 3:
new_image[i,j] = [255, 255, 255, 0.5]
We will edit the labels and values accordingly and use the same searchsorted solution -
label_ar = np.array([0,1,2,3]) # sorted label array
val_ar = np.array([
[255, 255, 255, 0],
[0, 255, 0, 0.5],
[0, 255, 255, 0.5],
[255, 255, 255, 0.5]])
You are right np.where is how you solve this problem. Where is a vectorized function so it should be much faster than your solution.
I'm making an assumption here that there is It doesn't have an elif that I'm aware of, but you can get around that by nesting where statements.
new_image = np.where(
image == 0,
[255, 255, 255, 0],
np.where(
image == 1,
[0, 255, 0, 0.5],
np.nan
)
)

Categories

Resources