Replacing part of image with a mask in opencv - python

I have an image that is 384x384 pixels.I want to mask out or replace everything in the middle of the image, so I just have the borders of the image. For this I have created a mask that is 352x352 pixels (just a black image). Now I want to place this mask in the middle of the image so it has a distance of 32 pixels to every corner.
I thought of something like this but it does not work:
mask = cv2.imread('C://Users//but//Desktop//mask.png')
hh, ww, dd = mask.shape
image = cv2.imread('C://Users//but//Desktop//Train//OK//train_image1.jpg')
x = 32
y = 352
print('left:', x, 'top:', y)
# put mask into black background image
mask2 = np.full_like(image, 255)
mask2[x:x + hh, y:y + ww] = mask
# apply mask to image
img_masked = cv2.bitwise_and(image, mask2)
cv2.imshow('masked image', img_masked)
cv2.waitKey(0)
cv2.destroyAllWindows()
The error I get is:
Traceback (most recent call last):
File "C:/Users/but/PycharmProjects/SyntheticDataWriter.py", line 17, in <module>
mask2[x:x + hh, y:y + ww] = mask
ValueError: could not broadcast input array from shape (352,352,3) into shape (352,32,3)

You can make the middle of the image black without creating a black image and overlaying it or anding it, just use Numpy slicing:
import cv2
# Load image and get its dimensions
im = cv2.imread('paddington.png', cv2.IMREAD_COLOR)
h,w = im.shape[0:2]
# Make middle black
im[32:h-32, 32:w-32, :] = 0
cv2.imwrite('result.png', im)
Here's a thumbnail of how he used to be in case anyone doesn't know:

This will help you to add a mask to your image.
import cv2
# image = cv2.imared("path to image") # (384x384X3)
image = np.ones((384, 384, 3), dtype=np.uint8) * 150 # creating 3d image with pixel value 150
h, w, c = image.shape
border_padding = 32 # border width and height you want to set
# we will set pixel value to zero from [32: 384-32, 32: 384-32]
image[border_padding:h-border_padding, border_padding:w-border_padding] = 0 # black mask image
Image

Related

how to extract individual pixel(color/rgb) value from one image and transfer to the second image

I'm trying to transfer pixel value from one image and transferring it to other image.
so, basically I have 2 images and my goal is to transfer colors of img1 to 2 according to the regions.
link to img1 img2 and expected image
here I am aware to extract color channel out of an image , but I am not able to achieve the required result. I'll highly appreciate any help.
my approach:
import cv2
import numpy as np
import os
import matplotlib.pyplot as plt
os.chdir('/kaggle/input/maskedoutput')
stroke_list = natsorted(os.listdir())
for i,v in enumerate(stroke_list):
image = cv2.imread(v, cv2.IMREAD_UNCHANGED)
if image.shape[2] == 4:
a1 = ~image[:,:,3]
image = cv2.add(cv2.merge([a1,a1,a1,a1]), image)
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
else:
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
plt.imshow((image))
plt.show()
copy = image.copy()
kernel = np.ones((15,15), np.uint8)
closing = cv2.morphologyEx(copy, cv2.MORPH_CLOSE, kernel)
img_erode = cv2.erode(closing, kernel, iterations=1)# to supress black outline
height, width, channel = img_erode.shape
for x1 in range(0,width):
for y1 in range(0,height):
channels_x1y1 = img_erode[y1,x1]
os.chdir('/kaggle/input/maskstrokes')
output = natsorted(os.listdir())
for idx,v1 in enumerate(output):
if(v==v1):
print(v, v1)
img_out = cv2.imread(v1, cv2.IMREAD_UNCHANGED)
subtracted = cv2.subtract(img_out, img_erode)
else:
continue
plt.imshow(cv2.cvtColor(subtracted, cv2.COLOR_BGR2RGB))
plt.show()
here i'm meaning to first erode the original coloured image in order to supress the black outline. Then next extracting color pixels and in the image2 after reading it i'm trying to subtract it with img1 the residual would be the colored outline, but this code is not working gives mte this error:
---------------------------------------------------------------------------
error Traceback (most recent call last)
/tmp/ipykernel_33/3647166721.py in <module>
43 print(v, v1)
44 img_out = cv2.imread(v1, cv2.IMREAD_UNCHANGED)
---> 45 subtracted = cv2.subtract(img_out, img_erode)
46 # if img_out.shape[2] == 4:
47 # a1 = ~img_out[:,:,3]
error: OpenCV(4.5.4) /tmp/pip-req-build-jpmv6t9_/opencv/modules/core/src/arithm.cpp:647: error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array' in function 'arithm_op'
another approach was to directly pick color pixels from image1 and directly transfer it to second image but as you can see the image has 3 parts with different colors and so its not happening
code:
os.chdir('/kaggle/input/maskedoutput')
stroke_list = natsorted(os.listdir())
for i,v in enumerate(stroke_list):
image = cv2.imread(v, cv2.IMREAD_UNCHANGED)
if image.shape[2] == 4:
a1 = ~image[:,:,3]
image = cv2.add(cv2.merge([a1,a1,a1,a1]), image)
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
else:
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
plt.imshow((image))
plt.show()
copy = image.copy()
kernel = np.ones((15,15), np.uint8)
closing = cv2.morphologyEx(copy, cv2.MORPH_CLOSE, kernel)
img_erode = cv2.erode(closing, kernel, iterations=1)# to supress black outline
height, width, channel = img_erode.shape
for x1 in range(0,width):
for y1 in range(0,height):
channels_x1y1 = img_erode[y1,x1]
os.chdir('/kaggle/input/maskstrokes')
output = natsorted(os.listdir())
for idx,v1 in enumerate(output):
if(v==v1):
print(v, v1)
img_out = cv2.imread(v1, cv2.IMREAD_UNCHANGED)
height2, width2, channel2 = img_out.shape
for x1 in range(0,width2):
for y1 in range(0,height2):
channels_x1y1 = img_out[y1,x1]
else:
continue
plt.imshow(cv2.cvtColor(img_out, cv2.COLOR_BGR2RGB))
plt.show()
Blocker image
I prepared a quick fix solution based on the expected output.
I used the following image as input:
Code:
img = cv2.imread('colored_objects.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# binary mask
mask = cv2.threshold(gray,10,255,cv2.THRESH_BINARY)[1]
# inverted binary mask
th = cv2.threshold(gray,10,255,cv2.THRESH_BINARY_INV)[1]
# finding external contours based on inverted binary mask
contours, hierarchy = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# create copy of original image to draw contours
img2 = img.copy()
In the following, we iterate through each contour.
For each contour:
get the centroid of the contour (centroid)
get the color at the centroid from original image (color)
draw the contour with that color on the copy of original image (img2)
code:
for c in contours:
M = cv2.moments(c)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
centroid = (cx, cy)
color = tuple(img[cy, cx])
color = ( int (color [ 0 ]), int (color [ 1 ]), int (color [ 2 ]))
print(color)
img2 = cv2.drawContours(img2, [c], 0, tuple(color), -1)
Now we subtract the original from the newly drawn image r. Based on mask, wherever pixels are white we assign white in r
r = img2 - img
r[mask == 255] = (255, 255, 255)
Update:
Expected result for the latest image. The green shade is present on the border as expected. This was obtained using the same code without any changes:

How to overlay Grayscale Mask on top of RGB image using Numpy and Matplotlib ( opencv or scikit image in case not possible)

I have 2 images from Carvana Image Dataset where image is jpg and mask is gif. I have converted the mask as grayscale as 0 or 1 and now want to overlay it over the image to see these 3 original, mask, superimposed side by side using matplotlib. What is the right way to do this?
from PIL import Image
def get_pair(image_path, mask_path):
image = np.array(Image.open(image_path).convert('RGB'))
mask = np.array(Image.open(mask_path).convert('L'), dtype = np.float32) # Mask should be Grayscale so each value is either 0 or 255
mask[mask == 255.0] = 1.0 # whereever there is 255, convert it to 1: (1 == 255 == White)
return image, mask
One way could be:
image, mask = data[0]
image = image / 255
mask = np.stack((mask,)*3, axis=-1)
blended = image * mask
plt.imshow(blended)
but it shows only the car and everything else as black
Below are the 2 images
and I want to plot these 3 as:
There might be a misconception in what you expect.
... but it shows only the car and everything else as black
This is how a binary mask usually operates.
The following selfcontained code (with the images from above saved accordingly) might explain what happens. Note the comments near blended1 = ...
from PIL import Image
import numpy as np
from matplotlib import pyplot as plt
def get_pair(image_path, mask_path):
image = np.array(Image.open(image_path).convert('RGB'))
mask = np.array(Image.open(mask_path).convert('L'), dtype = np.float32) # Mask should be Grayscale so each value is either 0 or 255
mask[mask == 255.0] = 1.0 # whereever there is 255, convert it to 1: (1 == 255 == White)
return image, mask
img, mask = get_pair("img.jpg", "mask.gif")
print(f"{img.shape=}") # -> img.shape=(1280, 1918, 3)
print(f"{mask.shape=}") # -> img.shape=(1280, 1918)
mask2 = np.stack((mask,)*3, axis=-1)
print(f"{mask2.shape=}") # -> img.shape=(1280, 1918, 3)
# rescale image
img = img /255
# set every pixel to (0, 0, 0) (black) where mask is 0 and
# keep every pixel unchanged where mask is 1
# this is how a mask is usually applied
blended1 = img*mask2
# set every pixel to (1, 1, 1) (white) where mask is 1 and
# keep every pixel unchanged where mask is 0
blended2 = np.clip(img+mask2, 0, 1)
fig, axx = plt.subplots(1, 4, figsize=(8, 18))
for ax, arr, title in zip(axx,
[img, mask, blended1, blended2],
["original", "mask", "blended1", "blended2"]):
ax.imshow(arr)
ax.axis("off")
ax.set_title(title)
plt.show()
The resulting image:

How to use OpenCV to crop circular image?

I was wondering if there was a way in openCV that would allow me to crop image between two circles, in a way that ignores everything in the smaller inner circle and everything outside of the larger circle. Like a donut shape.
Here is one way to do that in Python/OpenCV.
Read the input and get its dimensions
Define the radii of the two circles and the center coordinates
Create a white filled circle mask on a black background for each radius
Subtract the smaller radius mask from the larger radius mask
Put the resulting mask image into the alpha channel of the input
save the results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('lena.jpg')
hh, ww = img.shape[:2]
hh2 = hh // 2
ww2 = ww // 2
# define circles
radius1 = 25
radius2 = 75
xc = hh // 2
yc = ww // 2
# draw filled circles in white on black background as masks
mask1 = np.zeros_like(img)
mask1 = cv2.circle(mask1, (xc,yc), radius1, (255,255,255), -1)
mask2 = np.zeros_like(img)
mask2 = cv2.circle(mask2, (xc,yc), radius2, (255,255,255), -1)
# subtract masks and make into single channel
mask = cv2.subtract(mask2, mask1)
# put mask into alpha channel of input
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
result[:, :, 3] = mask[:,:,0]
# save results
cv2.imwrite('lena_mask1.png', mask1)
cv2.imwrite('lena_mask2.png', mask2)
cv2.imwrite('lena_masks.png', mask)
cv2.imwrite('lena_circle_masks.png', result)
cv2.imshow('image', img)
cv2.imshow('mask1', mask1)
cv2.imshow('mask2', mask2)
cv2.imshow('mask', mask)
cv2.imshow('masked image', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Smaller radius mask:
Larger radius mask:
Difference mask:
Resulting image:
You should use masks. Create an empty image of the same size, draw the larger circle with value '1', then the smaller with value '0', you'll get a "donut", then use that donut as a mask to copy the part of your image you're interested in.
This might help as a tutorial:
https://note.nkmk.me/en/python-opencv-numpy-alpha-blend-mask/

What's the most simple way to crop a circle thumbnail from an image?

I am trying to crop a centered (or not centered) circle from this image:
I stole this code from the existing questions regarding this topic on stack overflow, something goes wrong though:
import cv2
file = 'dog.png'
img = cv2.imread(file)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circle = cv2.HoughCircles(img,
3,
dp=1.5,
minDist=10,
minRadius=1,
maxRadius=10)
x = circle[0][0][0]
y = circle[0][0][1]
r = circle[0][0][2]
rectX = (x - r)
rectY = (y - r)
crop_img = img[rectY:(rectY+2*r), rectX:(rectX+2*r)]
cv2.imwrite('dog_circle.png', crop_img)
Output:
Traceback (most recent call last):
File "C:\Users\Artur\Desktop\crop_circle - Kopie\crop_circle.py", line 14, in <module>
x = circle[0][0][0]
TypeError: 'NoneType' object is not subscriptable
cv2.HoughCircles() seems to produce None instead of a cropped circle array. How do I fix this?
first: HoughCircles is used to detect circles on image, not to crop it.
You can't have circle image. Image is always rectangle but some of pixels can be transparent (alpha channel in RGBA) and programs will not display them.
So you can first crop image to have square and later add alpha channel with information which pixels should be visible. And here you can use mask with white circle on black background. At the end you have to save it as png or tiff because jpg can't keep alpha channel.
I use module PIL/pillow for this.
I crop square region in center of image but you can use different coordinates for this.
Next I create grayscale image with the same size and black background and draw white circle/ellipse.
Finally I add this image as alpha channel to cropped image and save it as png.
from PIL import Image, ImageDraw
filename = 'dog.jpg'
# load image
img = Image.open(filename)
# crop image
width, height = img.size
x = (width - height)//2
img_cropped = img.crop((x, 0, x+height, height))
# create grayscale image with white circle (255) on black background (0)
mask = Image.new('L', img_cropped.size)
mask_draw = ImageDraw.Draw(mask)
width, height = img_cropped.size
mask_draw.ellipse((0, 0, width, height), fill=255)
#mask.show()
# add mask as alpha channel
img_cropped.putalpha(mask)
# save as png which keeps alpha channel
img_cropped.save('dog_circle.png')
img_cropped.show()
Result
BTW:
In mask you can use values from 0 to 255 and different pixels may have different transparency - some of them can be half-transparent to make smooth border.
If you want to use it in HTML on own page then you don't have to create circle image because web browser can round corners of image and display it as circle. You have to use CSS for this.
EDIT: Example with more circles on mask.
from PIL import Image, ImageDraw
filename = 'dog.jpg'
# load image
img = Image.open(filename)
# crop image
width, height = img.size
x = (width - height)//2
img_cropped = img.crop((x, 0, x+height, height))
# create grayscale image with white circle (255) on black background (0)
mask = Image.new('L', img_cropped.size)
mask_draw = ImageDraw.Draw(mask)
width, height = img_cropped.size
mask_draw.ellipse((50, 50, width-50, height-50), fill=255)
mask_draw.ellipse((0, 0, 250, 250), fill=255)
mask_draw.ellipse((width-250, 0, width, 250), fill=255)
# add mask as alpha channel
img_cropped.putalpha(mask)
# save as png which keeps alpha channel
img_cropped.save('dog_2.png')
img_cropped.show()
This answer explains how to apply a mask. First, read in the image:
import cv2
import numpy as np
img = cv2.imread('dog.jpg')
Next create a mask, or a blank image that is the same size as the source image:
h,w,_ = img.shape
mask = np.zeros((h,w), np.uint8)
Then, draw a circle on the mask. Change these parameters based on where the face is:
cv2.circle(mask, (678,321), 5, 255, 654)
Finally, mask the source image:
img = cv2.bitwise_and(img, img, mask= mask)
Here is the mask:
And the output:
The idea is to create a black mask then draw the desired region to crop out in white using cv2.circle(). From there we can use cv2.bitwise_and() with the original image and the mask. To crop the result, we can use cv2.boundingRect() on the mask to obtain the ROI then use Numpy slicing to extract the result. For this example I used the center point derived from the image's width and height
import cv2
import numpy as np
# Create mask and draw circle onto mask
image = cv2.imread('1.jpg')
mask = np.zeros(image.shape, dtype=np.uint8)
x,y = image.shape[1], image.shape[0]
cv2.circle(mask, (x//2,y//2), 300, (255,255,255), -1)
# Bitwise-and for ROI
ROI = cv2.bitwise_and(image, mask)
# Crop mask and turn background white
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
x,y,w,h = cv2.boundingRect(mask)
result = ROI[y:y+h,x:x+w]
mask = mask[y:y+h,x:x+w]
result[mask==0] = (255,255,255)
cv2.imshow('result', result)
cv2.waitKey()

NumPy/OpenCV 2: how do I crop non-rectangular region?

I have a set of points that make a shape (closed polyline). Now I want to copy/crop all pixels from some image inside this shape, leaving the rest black/transparent. How do I do this?
For example, I have this:
and I want to get this:
*edit - updated to work with images that have an alpha channel.
This worked for me:
Make a mask with all black (all masked)
Fill a polygon with white in the shape of your ROI
combine the mask and your image to get the ROI with black everywhere else
You probably just want to keep the image and mask separate for functions that accept masks. However, I believe this does what you specifically asked for:
import cv2
import numpy as np
# original image
# -1 loads as-is so if it will be 3 or 4 channel as the original
image = cv2.imread('image.png', -1)
# mask defaulting to black for 3-channel and transparent for 4-channel
# (of course replace corners with yours)
mask = np.zeros(image.shape, dtype=np.uint8)
roi_corners = np.array([[(10,10), (300,300), (10,300)]], dtype=np.int32)
# fill the ROI so it doesn't get wiped out when the mask is applied
channel_count = image.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,)*channel_count
cv2.fillPoly(mask, roi_corners, ignore_mask_color)
# from Masterfool: use cv2.fillConvexPoly if you know it's convex
# apply the mask
masked_image = cv2.bitwise_and(image, mask)
# save the result
cv2.imwrite('image_masked.png', masked_image)
The following code would be helpful for cropping the images and get them in a white background.
import cv2
import numpy as np
# load the image
image_path = 'input image path'
image = cv2.imread(image_path)
# create a mask with white pixels
mask = np.ones(image.shape, dtype=np.uint8)
mask.fill(255)
# points to be cropped
roi_corners = np.array([[(0, 300), (1880, 300), (1880, 400), (0, 400)]], dtype=np.int32)
# fill the ROI into the mask
cv2.fillPoly(mask, roi_corners, 0)
# The mask image
cv2.imwrite('image_masked.png', mask)
# applying th mask to original image
masked_image = cv2.bitwise_or(image, mask)
# The resultant image
cv2.imwrite('new_masked_image.png', masked_image)
Input Image:
Mask Image:
Resultant output image:

Categories

Resources