cv::Mat::copyTo
performs one of two functions, depending on whether the output matrix has been initialized. If your output matrix is not initialized, using copyTo
with a mask creates a new output matrix, which is the same type as the input, and all values are 0 on all channels. As soon as this happens, the image data defined by the mask will be copied and the rest of the matrix will be set to 0. If your output matrix initialized and already consists of content, copyTo
copies above the pixels that are defined in the mask from the source, and leaves the pixels that were not part of the mask intact at the destination. Therefore, the replacement of pixels that are determined by the mask from the original image is copied to the output.
Since OpenCV now uses numpy
to interact with the library, it is very easy to make any of these methods. To distinguish from the other answer seen in this post, the first method can be implemented by simply multiplying the mask by the image element-wise. Assuming your input is called img
, and your binary mask is called mask
, where I assume the mask is 2D, just do the following:
import numpy as np import cv2 mask = ...
The above code assumes that both img
and mask
have the same number of channels. This is difficult if you use a color image as a source and a 2D mask, as I already suggested. Therefore, the total number of channels is 2, not 3, and therefore the above syntax will give you an error, since the sizes between them are no longer compatible. When using color images, you need to adapt. You can do this by adding a one-dimensional third dimension to the mask to use broadcast transmission.
import numpy as np import cv2 mask = ... # define mask here img = cv2.imread(...) # Define input image here # Create new image # Case #1 - Other image is grayscale and source image is colour if len(img.shape) == 3 and len(mask.shape) != 3: new_image = img * (mask[:,:,None].astype(img.dtype)) # Case #2 - Both images are colour or grayscale elif (len(img.shape) == 3 and len(mask.shape) == 3) or \ (len(img.shape) == 1 and len(mask.shape) == 1): new_image = img * (mask.astype(img.dtype)) # Otherwise, we can't do this else: raise Exception("Incompatible input and mask dimensions")
For the second approach, suppose we have another image called other_image
, where you want to copy the content in this image defined by your mask back to the target img
image. In this case, what you do first is to determine all locations in the mask that are not null using numpy.where
, then use these to index or slice the image, as well as where you want to copy. We must also remember the number of channels between two images, as in the first approach:
import numpy as np import cv2 mask = ... # define mask here img = cv2.imread(...) # Define input image here other_image = cv2.imread(...) # Define other image here locs = np.where(mask != 0) # Get the non-zero mask locations # Case #1 - Other image is grayscale and source image is colour if len(img.shape) == 3 and len(other_image.shape) != 3: img[locs[0], locs[1]] = other_image[locs[0], locs[1], None] # Case #2 - Both images are colour or grayscale elif (len(img.shape) == 3 and len(other_image.shape) == 3) or \ (len(img.shape) == 1 and len(other_image.shape) == 1): img[locs[0], locs[1]] = other_image[locs[0], locs[1]] # Otherwise, we can't do this else: raise Exception("Incompatible input and output dimensions")
Here is an example for both approaches. I am going to use a camera image that the standard test image is observed in most image processing algorithms.
I also artificially made the color of the image, although it was visualized as shades of gray, but the intensities will be copied to all channels. I'm also going to define a mask, which is just the top left side of 100 x 100, and so we will create an output image that copies only this subregion:
import numpy as np import cv2
When you use the first method and when you show the results, we get:
We see that we created an output image in which the upper left 100 x 100 contains our image data and the remaining pixels are 0. This depends on the mask locations that are set to True
. For the second approach, we will create another image that will be random with the same size as the input image, which spans from [0, 255]
for all channels.
As soon as we run the code with the second approach, I get this image now:
As you can see, the upper left corner of the image has been updated depending on the location of the masks that are set to True
.