How masking is applied in bitwise_operation in opencv? - python

I was looking at the documentation of the OpenCV and found something which I couldn't understand. I've tried to find it on the web but couldn't find anything satisfying. Can you please help me in a line of code?
Here is the code:
# Load two images
img1 = cv.imread('messi5.jpg')
img2 = cv.imread('opencv-logo-white.png')
# I want to put logo on top-left corner, So I create a ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
# Now create a mask of logo and create its inverse mask also
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
# Now black-out the area of logo in ROI
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)
# Take only region of logo from logo image.
img2_fg = cv.bitwise_and(img2,img2,mask = mask)
# Put logo in ROI and modify the main image
dst = cv.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()
What I actually don't understand are these two lines
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)
img2_fg = cv.bitwise_and(img2,img2,mask = mask)
What these lines actually do and how the masking will be applied?
If anyone can explain the masking being applied in the bitwise_and operation that would be really helpful.Thanks

If you look at the tutorial.
The mask is the black and white image of the OpenCV logo, it was created from applying a threshold to the OpenCV logo.
The bitwise_and operation is a logical and operation
In this case, it is taking two 8 bit numbers representing a pixel and applying the and operation on those numbers.
Documentation describes what this function does.
Since the first two parameters are the same (both roi or img2) the result would be the same image if a mask wasn't being used. Places, where the mask is black, are left the same as the destination image.
In this case, no destination image is provided, so OpenCV allocates a black image (zeros) for the destination image used in the function (this is generally how OpenCV works when a function is not provided with a Matrix).
Specifically img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv) will create a black matrix used in the function which later becomes the output img1_bg. Only the parts of this black image that match up with white pixels in mask_inv are filled with the pixels from roi.This means that in the mask_inv where there are white pixels. the roi value will be copied in the pure black image generated by the function in the corresponding coordinate.
Similarly img2_fg = cv.bitwise_and(img2,img2,mask = mask) will create a black matrix used in the function which later becomes the output img2_fg. Only the parts of this black image that match up with white pixels in mask are filled with the pixels from img2.
This makes it so when you add img1_bg and img2_fg the result is only the part of each image that is masked.
Personally, I think this is a confusing use of bitwise_and. I think to demonstrate the function of bitwise_and it would be clearer to remove the mask parameter as follows: img1_bg = cv.bitwise_and(roi, mask_inv). This would give the same result, zeros where the mask is black, and the ROI values where it is not since the mask has pixels that are all ones or all zeroes.
If you don't care to demonstrate bitwise_and usage, in python I think it would be clearer to use logical indexing as follows:
output = np.zeros(img1.shape, np.uint8)
output[mask_inv] = img1_bg[mask_inv]
output[mask] = img2_fg[mask]

Related

How can I remove these parallel lines noise on my image using opencv

I'm new to opencv and I m trying to remove all these diagonal parallel lines that are noise in my image.
I have tried using HoughLinesP after some erosion/dilatation but the result is poo (and keeping only the one with a near 135 degree angle).
img = cv2.imread('images/dungeon.jpg')
ret,img = cv2.threshold(img,180,255,0)
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
eroded = cv2.erode(img,element)
dilate = cv2.dilate(eroded, element)
skeleton = cv2.subtract(img, dilate)
gray = cv2.cvtColor(skeleton,cv2.COLOR_BGR2GRAY)
minLineLength = 10
lines = cv2.HoughLinesP(gray, 1, np.pi/180, 1, 10, 0.5)
for line in lines:
for x1,y1,x2,y2 in line:
angle = math.atan2(y2-y1,x2-x1)
if (angle > -0.1 and angle < 0.1):
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),1)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
My thinking here was to detect these lines in order to remove them afterwards but I m not even sure that's the good way to do this.
I guess you are trying to get the contours of the walls, right? Here’s a possible path to the solution using mainly spatial filtering. You will still need to clean the results to get where you want. The idea is to try and compute a mask of the parallel lines (high-frequency noise) of the image and calculate the difference between the (binary) input and this mask. These are the steps:
Convert the input image to grayscale
Apply Gaussian Blur to get rid of the high-frequency noise you are trying to eliminate
Get a binary image of the blurred image
Apply area filters to get rid of everything that is not noise, to get a noise mask
Compute the difference between the original binary mask and the noise mask
Clean up the difference image
Compute contours on this image
Let’s see the code:
import cv2
import numpy as np
# Set image path
path = "C://opencvImages//"
fileName = "map.png"
# Read Input image
inputImage = cv2.imread(path+fileName)
# Convert BGR to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Apply Gaussian Blur:
blurredImage = cv2.GaussianBlur(grayscaleImage, (3, 3), cv2.BORDER_DEFAULT)
# Threshold via Otsu:
_, binaryImage = cv2.threshold(blurredImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Save a copy of the binary mask
binaryCopy = cv2.cvtColor(binaryImage, cv2.COLOR_GRAY2BGR)
This is the output:
Up until now you get this binary mask. The process so far has smoothed the noise and is creating thick black blobs where the noise is located. Again, the idea is to generate a noise mask that can be subtracted to this image.
Let’s apply an area filter and try to remove the big white blobs, which are NOT the noise we are interested to preserve. I’ll define the function towards the end, for now I just want to present the general idea:
# Set the minimum pixels for the area filter:
minArea = 50000
# Perform an area filter on the binary blobs:
filteredImage = areaFilter(minArea, binaryImage)
The filter will suppress every white blob that is above the minimum threshold. The value is big because in this particular case we are interested in preserving only the black blobs. This is the result:
We have a pretty solid mask. Let’s subtract this from the original binary mask we created earlier:
# Get the difference between the binary image and the mask:
imgDifference = binaryImage - filteredImage
This is what we get:
The difference image has some small noise. Let’s apply the area filter again to get rid of it. This time with a more traditional threshold value:
# Set the minimum pixels for the area filter:
minArea = 20
# Perform an area filter on the binary blobs:
filteredImage = areaFilter(minArea, imgDifference)
Cool. This is the final mask:
Just for completeness. Let’s compute contours on this input, which is very straightforward:
# Find the big contours/blobs on the filtered image:
contours, hierarchy = cv2.findContours(filteredImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours on the mask image:
cv2.drawContours(binaryCopy, contours, -1, (0, 255, 0), 3)
Let’s see the result:
As you see it is not perfect. However, there’s still some room for improvement, perhaps you can polish a little bit more this idea to get a potential solution. Here's the definition and implementation of the areaFilter function:
def areaFilter(minArea, inputImage):
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(inputImage, connectivity=4)
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
return filteredImage

Convert image to binary doesn't shows white lines of a soccer field

I am inspired by the following blogpost, however I am struggling with step 2/3.
I want to creates a binary image from a gray image based on the threshold values and ultimately displaying all white lines on the image. My desired output looks as follows:
First, I want to isolate the soccer field by using colour-thresholding and morphology.
def isolate_field(img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# find green pitch
light_green = np.array([40, 40, 40])
dark_green = np.array([70, 255, 255])
mask = cv2.inRange(hsv, light_green, dark_green)
# removing small noises
kernel = np.ones((5, 5), np.uint8)
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# apply mask over original frame
return cv2.bitwise_and(frame, frame, mask=opening)
This gives the following output:
I am happy with the results so far, but because of the large shadow I am struggling with the image-processing when I grayscale the picture. As a result, the binary thresholding is based on the sunny part in the upper-left corner instead of the white lines around the soccer field.
Following the methodology on the tutorials I get the following output for the simple thresholding:
and adaptive thresholding:
and finally, Otsu's thresholding:
How can I make sure that the white lines become more visible? I was thinking about cropping the frame so I only see the field and then use a mask based on the color white. That didn't work out unfortunately.
Help is much appreciated,
You can modify inRange to also exclude saturated colors (meaning the greens). I don't have your original image, so I used your intermediate result:
The result of inRange is the binary image you want. I expect you can achieve better results with the original image. I used this script in the image - which makes it easy to search for good HSV values.

How to add a mask on an image blurring their intersection?

I have a mask and a background image. I want to add this mask on the background image and blurring (or perhaps something like cv2.inpaint()) the intersection to make it more natural, but am blocked for the bluring effect, any help would be highly appreciated.
More details:
I have two RGB images. The first image (foreground) is associated with a binary mask, which I would like to add on the second image (background).
The issue is that when we look at the final image we clearly see which part was added on the background image. Hence, I would like to add a blurring effect at the intersection of the mask and the background image. For now my code looks like:
#foreground image: we'll use only the mask part
#background image: where we will add the mask
foreground = cv2.imread(path1)
background = cv2.imread(path2)
#Convert to float
foreground = foreground.astype(float)
background = background.astype(float)
mask = mask.astype(float)
#Multiply the foreground with the mask
foreground = cv2.multiply(mask, foreground)
#Multiply the background with everywhere except with mask
background = cv2.multiply(1.0 - mask, background)
#Add the masked foreground to background image
outImage = cv2.add(foreground, background)
I could not find a straightforward way to do it, but I guess their should have one. A lot of related answer on internet works by thresholding to some pixel value, but this can not be used here. For now the easiest way I found is:
create a mask of the part I want to blurr
blurr the final images (background +foreground mask)
take from the blurr images only the part of the mask of 1) and add it the initial final image (background +foreground mask)
Before doing this, I was wondering if someone would have some advices.
The way I am doing it so far works well, but I am staying open to any other better solution!
Here's mine:
def foreground_background_into1(background, foreground, with_smooth=True, thickness=3, mask=[]):
'''will add the foreground image to the background image to create a new image, with smooth intersection
-foreground image: this image must be either black where there is no mask, or the mask parameter must be specified in
the mask parameter
-background image: image on which we will add the mask
-mask: binary mask if foreground image does not already contain this information
Note: not tested with mask'''
#make a copy for the smooth part
img = foreground.copy()
#create binary mask (as needed to multiply with the backgound) from foreground image if not existing (replacing all value
#bigger than 0 to 1)
if len(mask)==0:
_,mask = cv2.threshold(foreground,1,1,cv2.THRESH_BINARY)
#verification
if foreground.shape!=background.shape:
raise Warning("the foreground is not of same shape as background, we will convert it")
foreground = imresize(foreground, size=background.shape)
#if mask has one channel add two others
if len(mask.shape)==2:
mask = skimage.color.gray2rgb(mask)
#add foreground to background
foreground = foreground.astype(float)
background = background.astype(float)
mask = mask.astype(float)
foreground = cv2.multiply(mask, foreground)
background = cv2.multiply(1 - mask, background) #is the initial background with black where the mask of forground
outImage = cv2.add(foreground, background)
result = outImage.astype(np.uint8)
if with_smooth:
#find contour
foreground_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(foreground_gray,1,255,0)
_, contours, __ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#create intersection_mask
intersection_mask = cv2.drawContours(np.zeros(foreground.shape, np.uint8),
contours,-1,(0,255,0),thickness)
#inpaint the contour in the first final image
intersection_mask = cv2.cvtColor(intersection_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1,1))
intersection_mask = cv2.dilate(intersection_mask, kernel, iterations=1)
result = cv2.inpaint(result,intersection_mask,1,cv2.INPAINT_TELEA)
return(result)
I am basically adding the mask on the background image, and then smooth the intersection part by inpaint with openCV the contour of the mask.

how to draw outlines of objects on an image to a separate image

i am working on a puzzle, my final task here is to identify edge type of the puzzle piece.
as shown in the above image i have mange to rotate and crop out every edge of the piece in same angle. my next step is to separate the edge line into a separate image like as shown in the image bellow
then to fill up one side of the line with with a color and try to process it to decide what type of edge it is.
i dont see a proper way to separate the edge line from the image for now.
my approach::
one way to do is scan pixel by pixel and find the black pixels where there is a nun black pixel next to it. this is a code that i can implement. but it feels like a primitive and a time consuming approach.
so if there you can offer any help or ideas, or any completely different way to detect the hollows and humps.
thanks in advance..
First convert your color image to grayscale. Then apply a threshold, say zero to obtain a binary image. You may have to use morphological operations to further process the binary image if there are holes. Then find the contours of this image and draw them to a new image.
A simple code is given below, using opencv 4.0.1 in python 2.7.
bgr = cv2.imread('puzzle.png')
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
_, roi = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
cv2.imwrite('/home/dhanushka/stack/roi.png', roi)
cont = cv2.findContours(roi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
output = np.zeros(gray.shape, dtype=np.uint8)
cv2.drawContours(output, cont[0], -1, (255, 255, 255))
# removing boundary
boundary = 255*np.ones(gray.shape, dtype=np.uint8)
boundary[1:boundary.shape[0]-1, 1:boundary.shape[1]-1] = 0
toremove = output & boundary
output = output ^ toremove

explain arguments meaning in res = cv2.bitwise_and(img,img,mask = mask)

I am trying to extract blue colour of an input image. For that I create a blue HSV colour boundary and threshold HSV image by using the command
mask_img = cv2.inRange(hsv, lower_blue, upper_blue)
After that I used a bitwise_and on the input image and the threshold image by using
res = cv2.bitwise_and(img, img, mask = mask_img)
Where img is the input image. I got this code from opencv. But I didn't understand why are three arguments used in bitwise_and and what actually each arguments mean? Why the same image is used at src1 and src2 ?
And also what is the use of mask keyword here? Please help me to find out the answer
The basic concept behind this is the value of color black ,it's value is 0 in OPEN_CV.So black + anycolor= anycolor because value of black is 0.
Now suppose we have two images one is named img1 and other is img2.
img2 contains a logo which we want to place on the img1. We create threshold and then the mask and mask_inv of img2,and also create roi of img1.
Now we have to do two things to add the logo of img2 on img1.
We create background of roi as img1_bg with help of : mask_inv,mask_inv will have two region one black and one white, in the white region we will put img1 part and leave black as it is-
img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
In your question you have used directly the mask of the img created
res = cv2.bitwise_and(img,img,mask = mask_img)
and in img2 we need to create the logo as foreground of roi ,
img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
here we have used mask layer , the logo part of img2 gets filled in the white part of mask
Now when we add both we get a perfect combined roi
For full description and understanding visit:
OPEN CV CODE FILES AND FULL DESCRIPTION
The operation of "And" will be performed only if mask[i] doesn't equal zero, else the the result of and operation will be zero. The mask should be either white or black image with single channel. you can see this link
http://docs.opencv.org/2.4.13.2/modules/core/doc/operations_on_arrays.html?highlight=bitwise#bitwise-and
what is actually each arguments mean?
res = cv2.bitwise_and(img,img,mask = mask_img)
src1: the first image (the first object for merging)
src2: the second image (the second object for merging)
mask: understood as rules to merge. If region of image (which is gray-scaled, and then masked) has black color (valued as 0), then it is not combined (merging region of the first image with that of the second one), vice versa, it will be carried out. In your code, referenced image is "mask_img".
In my case, my code is correct, when it makes white + anycolor = anycolor
import cv2
import numpy as np
# Load two images
img1 = cv2.imread('bongSung.jpg')
img2 = cv2.imread('opencv.jpg')
# I want to put logo on top-left corner, so I create a ROI
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
# NOw we need to create a mask of the logo, mask is conversion to grayscale of an image
img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 220, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('mask', mask)
mask_inv = cv2.bitwise_not(mask)
#cv2.imshow("mask_inv", mask_inv)
#When using bitwise_and() in opencv with python then white + anycolor = anycolor; black + anycolor = black
img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
#cv2.imshow("img1_bg", img1_bg)
cv2.imshow("img2", img2)
img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
cv2.imshow('img2_fg', img2_fg)
dst = cv2.add(img1_bg,img2_fg)
img1[0:rows, 0:cols] = dst
#cv2.imshow("Image", img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
From above answers we may know the definitions of the parameters of bitwise_and(), but they all do not answer the other question
Why the same image is used at src1 and src2 ?
This question should be caused by the too simplified function definition in the document of OpenCV, it may be ambiguous to some people, in the document the bitwise_and() is defined as
dst(I)=sur1(I) ^ sur2(I), if mask(I) != 0, where ^ represents the 'and' operator
from this definition at first sight I cannot get the picture about how to process the dst(I) when the mask(I) is 0.
From the test result, I think that it should give a more clear function definition as
dst(I)=sur1(I) ^sur2(I), if mask(I) != 0,
otherwise the dst(I) keep its original value and the default value of all elements of the dst array is 0.
Now we may know that using the same image for sur1 and sur2, it will only keep the original image parts in the area of mask(I) !=0 and the other area shows the part of the dst image (as the mask shape)
Additionally for other bitwise operations the definitions should be the same as above, they also need to add the otherwise condition and the default value description of the dst array
The below link explains clearly the bitwise operation and also the significance of each parameters.
http://opencvexamples.blogspot.com/2013/10/bitwise-and-or-xor-and-not.html
void bitwise_and(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray())
Calculates the per-element bit-wise conjunction of two arrays or an array and a scalar.
Parameters:
src1 – first input array or a scalar.
src2 – second input array or a scalar.
src – single input array.
value – scalar value.
dst – output array that has the same size and type as the input arrays.
mask – optional operation mask, 8-bit single channel array, that specifies elements of the output array to be changed
Regarding using img twice, my guess is that we don't really care what img[i] and img[i] is, as it's just img[i] for binary. What matters is that, as mentioned by Mohammed Awney, when the mask is 0, we make img[i] be 0, otherwise we leave the pixel alone. This is a way to make certain pixels in img black, according to our mask.
bitwise_and ( InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray()
)
src1 first input array or a scalar.
src2 second input array or a scalar.
dst output array that has the same size and type as the input arrays.
mask optional operation mask, 8-bit single channel array, that specifies elements of the output array to be changed.
dst(I)=src1(I)∧src2(I)if mask(I)≠0
and mask operate on dst
computes bitwise conjunction of the two arrays (dst = src1 & src2) Calculates the per-element bit-wise conjunction of two arrays or an array and a scalar.

Categories