I am having a bunch of mask (object is white, non-object is black) bounded by their bounding box as a separate image, and I'm trying to put them back to their original positions on the original image. What I have in mind right now is:
Create a black image of the same size of the original image.
Add the value of each mask with the value of the coordinate of the bounding box on the original image together.
Could anyone tell me if I am heading in the right path, is there any better way to do this?.
Below is roughly my implementation
import cv2
black_img = np.zeros((height,width)) # A image that is of the size of the original but is all black
mask = cv2.imread("mask.png")
bbox = [x1, y1, x2, y2] # Pretend that this is a valid bounding box coordinate on the original image
black_img[y1:y2, x1:x2] += mask
For example:
I have the first image which is one of my masks. Its size is of the same of the bounding box on the original image. I'm trying merge each mask back together so that I achieved something like the second image.
One of the mask:
After merging all the mask:
I am assuming the mask is 0 and 1's and your image is grayscale. Also, for each small_mask, you have a corresponding bbox.
mask = np.zeros((height,width))
for small_mask, bbox in zip(masks, bboxes):
x1, y1, x2, y2 = bbox
mask[y1:y2, x1:x2] += small_mask
mask = ((mask>=1)*255.0).astype(np.uint8)
Now you combined all the small masks together.
The last line:
My assumption was somehow two masks may intersect. So those intersection may have values more than 1. mask >= 1 tells me that the pixels that are more than 0 are gonna be all on.
I multiplied that by 255.0 because I wanted to make it white. You won't be able to see 1's in a grayscale image.
(mask >= 1)*255.0 expanded the range from [0-1] to [0-255]. But this value is float which is not image type.
.astype(np.uint8) converts the float to uint8. Now you can do all the image operations without any problem. When it is float, you may face a lot of issues, like plotting, saving, all will cause some issues.
Related
I have 2 images - one of the images is binarized i.e. each pixel is either black or white, and the other image is a standard RGB image. Both images are of the same size. For all the white pixels in the first image, I would like to take the corresponding pixels in the RGB image and attach them in place of the white pixels. How can I do this in Python?
Use the binarized image as a binary index for the pixel coordinates:
import numpy as np
my_source_image = np.random.randint(0, 255, (480,640,3), np.uint8) # defined somewhere, assumed to have shape [H,W,3]
my_binarized_image = np.random.randint(0, 2, (480,640), np.uint8) # defined somewhere, assumed to have shape [H,W]
pixel_idx = my_binarized_image.astype(bool)
my_dest_image = np.zeros_like(my_source_image)
my_dest_image[pixel_idx,:] = my_source_image[pixel_idx]
Note that I define the destination image as all-zeroes (if you want a different background color, simply initialize to the color value instead) and then fill in the relevant pixels instead of reusing my_binarized_image as you see to require in your question, because the destination image needs three channels, while the binarized image only has one.
Alternatively, as long as the "background" color is black, you can avoid preallocating a second image and simply zero-out the pixels in the first image instead:
my_dest_image = my_source_image * pixel_idx[...,None]
(The [...,None] adds an extra dimension at the end of pixel_idx to make the pixel-wise multiplication possible)
Images such as this one (https://imgur.com/a/1B7nQnk) should be cropped into individual cells. Sadly, the vertical distance is not static, so a more complex method needs to be applied. Since the cells have alternating background colors (grey and white, maybe not visible at low contrast monitors), I thought it might be possible to get the coordinates of the boundaries between white and grey, with which accurate cropping can be accomplished. Is there a way to, e.g., transform the image into a giant two dimensional array, with digits corresponding to the color of the pixel ?... so basically:
Or is there another way?
Here's a snippet that shows how to access the individual pixels of an image. For simplicity, it first converts the image to grayscale and then prints out the first three pixels of each row. It also indicates where the brightness of the first pixel is different from that pixel in that column on the previous row—which you could use to detect the vertical boundaries.
You could do something similar over on the right side to determine where the boundaries are on that side ( you've determined the vertical ones).
from PIL import Image
IMAGE_FILENAME = 'cells.png'
WHITE = 255
img = Image.open(IMAGE_FILENAME).convert('L') # convert image to 8-bit grayscale
WIDTH, HEIGHT = img.size
data = list(img.getdata()) # convert image data to a list of integers
# convert that to a 2D list (list of lists of integers)
data = [data[offset:offset+WIDTH] for offset in range(0, WIDTH*HEIGHT, WIDTH)]
prev_pixel = WHITE
for i, row in enumerate(range(HEIGHT)):
possible_boundary = ' boundary?' if data[row][0] != prev_pixel else ''
print(f'row {i:5,d}: {data[row][:3]}{possible_boundary}')
prev_pixel = data[row][0]
I am using drawContours to make a mask for extracting ROI.
I have already defined four points and a zero mask for drawing the contour.
The output mask of the drawContours function is sort of a trapezoid shape which is what I want.
However, when I use this mask to do bitwise_and with the image,
the result isn't really the same shape with the mask.
The edge of the shape is obviously jagged.
Here is my python code snippet:
hull2 = cv2.convexHull(crop1)
mask10 = np.zeros(image.shape[:2], dtype = "uint8")
print(len(hull2))
cv2.drawContours(mask10, [hull2], -1,255, -1,cv2.LINE_AA)
cv2.imshow("mask10",mask10)
cv2.waitKey(0)
crop = cv2.bitwise_and(image, image, mask=mask10)
cv2.imshow("crop",crop)
cv2.waitKey(0)
cv2.drawContours(image, [hull2], -1, (0, 255, 0), -1,cv2.LINE_AA)
cv2.imshow("mask+img",image)
cv2.waitKey(0)
And here is a picture showing the result: "crop" is the ROI result image
Thanks for anyone trying to help.
The reason you are getting jagged edges while your mask looks like it has smooth edges is because you are using the anti-aliasing flag on drawContours( - ,cv2.LINE_AA) which fills in the surrounding of the jagged edges with darker pixels creating a gradient that fools your eye into thinking its a smooth edge.
Why does this matter? when you use bitwise_and with a mask, any value in the mask greater than 0 is evaluated as "True" and the corresponding pixel in the image will be selected.
So those extra AA pixels despite being a smaller gray value than 255, are expanding the edge of the mask, creating your jagged edge in crop. To emulate this, do mask10[mask10 > 0] = 255; cv2.imshow('mask10', mask10) and it should have the same shape as crop.
Now as a possible solution to your problem, you could use alpha blending to use the gradient (darkened intensity) of those extra AA pixels to darken the crop image edge pixels.
mask_float = cv2.cvtColor(mask10, cv2.COLOR_GRAY2BGR).astype('float32') / 255
image_float = image.astype('float32')
crop = cv2.multiply(image_float, mask_float).astype('uint8')
First we convert mask10 to a 3 channel array so that we can apply the alpha blending to all 3 BGR channels of the image.
Then we normalize the mask to a [0-1] range as we will need to multiply the values in the next step and dtype uint8 doesnt allow greater than 255. So first converting to float32 then dividing by 255. (we could potentially use cv2.normalize() but numpy should be alot faster)
we then convert the image to float32 to allow for multiplication with the mask.
then we multiply the image with the mask to get an alpha blended image of foreground to a black background and convert it back to uint8 for opencv.
Now since the BGR values are converted from float32 to uint8, it will discard the decimal values which will cause a negligible change in color. Also, I'm not 100% sure but there might be a small change in color due to multiplying each channel individually by the same value (eg: 20%) or it could be fine and im just overthinking it? But that only applies to those darkened AA pixels, the effect should also be negligible and we are already modifying it from the original anyways so it should be fine!
As an alternative, you could also convert the image to HLS and multiply the mask to the L-channel only. I believe that should be more true to the image's colors on those edges if that is very important, and the slower speed is permissible
skimage rotate function create "outer" pixels, no matter how this pixels extrapolated (wrap, mirror, constant, etc) - they are fake, and can affect statistical analysis of image. How can I get mask of this pixels to ignore them in analysis?
mask_val = 2
rotated = skimage.transform.rotate(img, 15, resize=True, cval=mask_val,
preserve_range=False)
mask = rotated == mask_val
Idea: pick a value for the mask which doesn't appear in the image, then obtain mask by checking for equality with this value. Works well when image pixels are normalized floats. rotate above transforms image pixels to normalized floats internally thanks to preserve_range=False (this is default value, I specified it just to make point that without it this wouldn't work).
I'm cropping an image like this:
self.rst = self.img_color[self.param_a_y:self.param_b_y,
self.param_a_x:self.param_b_x:, ]
How do I copy this image back to the original one. The data I have available are the coordinates of the original image, which makes the center of the crop.
Seems like there's nocopy_to() function for python
I failed myself getting copy_to() working a few days ago, but came up with a difeerent solution: You can uses masks for this task.
I have an example at hand which shows how to create a mask from a defined colour range using inrange. With that mask, you create two partial images (=masks), one for the old content and one for the new content, the not used area in both images is back. Finally, a simple bitwise_or combines both images.
This works for arbitrary shapes, so you can easily adapt this to rectangular ROIs.
import cv2
import numpy as np
img = cv2.imread('image.png')
rows,cols,bands = img.shape
print rows,cols,bands
# Create image with new colour for replacement
new_colour_image= np.zeros((rows,cols,3), np.uint8)
new_colour_image[:,:]= (255,0,0)
# Define range of color to be exchanged (in this case only one single color, but could be range of colours)
lower_limit = np.array([0,0,0])
upper_limit = np.array([0,0,0])
# Generate mask for the pixels to be exchanged
new_colour_mask = cv2.inRange(img, lower_limit, upper_limit)
# Generate mask for the pixels to be kept
old_image_mask=cv2.bitwise_not(new_colour_mask)
# Part of the image which is kept
img2= cv2.bitwise_and(img,img, old_image_mask)
# Part of the image which is replaced
new_colour_image=cv2.bitwise_and(new_colour_image,new_colour_image, new_colour_mask)
#Combination of the two parts
result=cv2.bitwise_or(img2, new_colour_image)
cv2.imshow('image',img)
cv2.imshow('mask',new_colour_mask)
cv2.imshow('r',result)
cv2.waitKey(0)