I have a text detector which outputs polygon coordinates of detected text:
I am using below loop to show how the detected text looks like with bounding boxes:
for i in range(0, num_box):
pts = np.array(boxes[0][i],np.int32)
pts = pts.reshape((-1,1,2))
print(pts)
print('\n')
img2 = cv2.polylines(img,[pts],True,(0,255,0),2)
return img2
Each pts stores all coordinates of a polygon, for one text box detection:
pts =
[[[509 457]]
[[555 457]]
[[555 475]]
[[509 475]]]
I would like to convert the area inside the bounding box described by pts to grayscale using:
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
However I am not sure how should I provide the image argument in above gray_image as I want to convert only the area described by pts to grayscale and not the entire image (img2). I want the rest of the image to be white.
From my understanding you want to convert the content of the bounding box to grayscale, and set the rest of the image to white (background).
Here would be my solution to achieve that:
import cv2
import numpy as np
# Some input image
image = cv2.imread('path/to/your/image.png')
# Some pts
pts = np.array([[60, 40], [340, 40], [340, 120], [60, 120]])
# Get extreme x, y coordinates from box
x1 = pts[0][0]
y1 = pts[0][1]
x2 = pts[1][0]
y2 = pts[2][1]
# Build output; initialize white background
image2 = 255 * np.ones(image.shape, np.uint8)
image2[y1:y2, x1:x2] = cv2.cvtColor(cv2.cvtColor(image[y1:y2, x1:x2], cv2.COLOR_BGR2GRAY), cv2.COLOR_GRAY2BGR)
# Show bounding box in original image
cv2.polylines(image, [pts], True, (0, 255, 0), 2)
cv2.imshow('image', image)
cv2.imshow('image2', image2)
cv2.waitKey(0)
cv2.destroyAllWindows()
The main "trick" is to use OpenCV's cvtColor method twice just on the region of interest (ROI) of the image, first time converting BGR to grayscale, and then grayscale back to BGR. Accessing rectangular ROIs in "Python OpenCV images" is done by proper NumPy array indexing and slicing. Operations solely on these ROIs are supported by most OpenCV functions (Python API).
EDIT: If your final image is a plain grayscale image, the backwards conversion of course can be omitted!
These are some outputs, I generated with my "standard image":
Hope that helps!
Related
I'm using OpenCV to detect Pneumonia in chest-x-ray using Image Processing, so I need to remove the attached area to the image border to get the lung only, can anyone help me code this in python?
This image explains what I want this image after applying this methods: resized, Histogram Equalization, otsu Thresholded and inverse binary Thresholded, morphological processes(opening then closing)
This is the Original Image Original Image
This is how I would approach the problem in Python/OpenCV. Add a white border all around, flood fill it with black to replace the white, then remove the extra border.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('lungs.jpg')
h, w = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# add 1 pixel white border all around
pad = cv2.copyMakeBorder(gray, 1,1,1,1, cv2.BORDER_CONSTANT, value=255)
h, w = pad.shape
# create zeros mask 2 pixels larger in each dimension
mask = np.zeros([h + 2, w + 2], np.uint8)
# floodfill outer white border with black
img_floodfill = cv2.floodFill(pad, mask, (0,0), 0, (5), (0), flags=8)[1]
# remove border
img_floodfill = img_floodfill[1:h-1, 1:w-1]
# save cropped image
cv2.imwrite('lungs_floodfilled.png',img_floodfill)
# show the images
cv2.imshow("img_floodfill", img_floodfill)
cv2.waitKey(0)
cv2.destroyAllWindows()
You can try using a morphological reconstruction with a border as a marker. This is an analogue of the function imclearborder from Matlab or Octave.
import cv2
import numpy as np
img = cv2.imread('5R0Zs.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 40, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((7,7),np.uint8)
kernel2 = np.ones((3,3),np.uint8)
marker = thresh.copy()
marker[1:-1,1:-1]=0
while True:
tmp=marker.copy()
marker=cv2.dilate(marker, kernel2)
marker=cv2.min(thresh, marker)
difference = cv2.subtract(marker, tmp)
if cv2.countNonZero(difference) == 0:
break
mask=cv2.bitwise_not(marker)
mask_color = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
out=cv2.bitwise_and(img, mask_color)
cv2.imwrite('out.png', out)
cv2.imshow('result', out )
cv2.waitKey(0) # waits until a key is pressed
cv2.destroyAllWindows()
I have an image of an IC die and I want to cut out the marking in the center.The marking is always at this specific position above the circle at the bottom left.
The idea is to first find the circle position which I already accomplished with the hough circle transformation. Now I want to cut out the part where the marking is. It should ideally be a not a square or rectangle but something more like in the image:
This is a part of my code:
cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(morph_image, cv2.HOUGH_GRADIENT, 1.3, 20, param1=50, param2=25, minRadius=15,
maxRadius=19)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
# Zeichne äußeren Kreis
cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
# Zeichne Kreiszentrum
cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
# Tupel mit x- und y-Koordinaten des Kreiszentrums
circle_center = (i[0], i[1])
print('Die Koordinaten des Kreiszentrums lauten: ', circle_center)
"""cv2.imshow('Kreis', cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()"""
else:
circle_center = None
print('Kein Kreis gefunden')
"""cv2.imshow('Kein Kreis', cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()"""
so my cicle center has the center position of my circle (e.g. (124, 370)). How can I cut out this part of the image automatically? Can I somehow crop it out? Ideally I would want to crop the marking out into another image to inspect it separately but the normal cropping approach with marking_img = img[y:y+h, x:x+w] wouldn't work I guess.
EDIT:
Here is the original image:
The output should be like the first image and if it is possible something like this:
So in the end I would want to have 2 images: One image with just the die without the marking and one image with just the marking
Here is one way in Python/OpenCV.
Read the image
Read the mask (separately created one time from your other image)
Convert the mask to gray and threshold it to binary, invert it and make it 3 channels
Get the center of the circle from your own code. (I have just measured it manually)
Set the expected x,y offsets of the bottom of the region of text from the center of the circle
Compute the expected top left corner of the mask from the center of the circle, the offsets and the height of the mask image
Put the mask into black image the size of the input at that location
Apply the new mask to the image to make the rest of the image black
Crop out the region of interest from the top left corner and the size of the original mask
OPTIONALLY, crop the original image
Save the results
Input image:
Prepared mask image:
import cv2
import numpy as np
# read image
img = cv2.imread('die.jpg')
ht, wd, cc = img.shape
# read mask as grayscale
mask = cv2.imread('die_mask.png', cv2.IMREAD_GRAYSCALE)
# threshold mask and invert
mask = cv2.threshold(mask,0,255,cv2.THRESH_BINARY)[1]
mask = 255 - mask
hh, ww = mask.shape
# make mask 3 channel
mask = cv2.merge([mask,mask,mask])
# set circle center
cx = 62
cy = 336
# offsets from circle center to bottom of region
dx = -20
dy = -27
# compute top left corner of mask using size of mask and center and offsets
left = cx + dx
top = cy + dy - hh
# put mask into black background image
mask2 = np.zeros_like(img)
mask2[top:top+hh, left:left+ww] = mask
# apply mask to image
img_masked = cv2.bitwise_and(img, mask2)
# crop region
img_masked_cropped = img_masked[top:top+hh, left:left+ww]
# ALTERNATE just crop input
img_cropped = img[top:top+hh, left:left+ww]
cv2.imshow('image', img)
cv2.imshow('mask', mask)
cv2.imshow('mask2', mask2)
cv2.imshow('masked image', img_masked)
cv2.imshow('masked cropped image', img_masked_cropped)
cv2.imshow('cropped image', img_cropped)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save results
cv2.imwrite('die_mask_inserted.jpg', mask2)
cv2.imwrite('die_masked_image.jpg', img_masked)
cv2.imwrite('die_masked_cropped.jpg', img_masked_cropped)
cv2.imwrite('die_cropped.jpg', img_cropped)
Mask inserted in black image:
Masked image:
Crop of masked image:
(Optional) Crop of input image:
Lets make it straightforward.
I have private project to block or pixelate image using boundary box in open-cv, something like censoring image, inspired from this paper:
https://www.researchgate.net/publication/325746502_Seamless_Nudity_Censorship_an_Image-to-Image_Translation_Approach_based_on_Adversarial_Training
I have found the way to classify the area of censor using Keras, but still don't know the way how to use the boundary box to pixelate the classified area, and overlay it to original image. Any help are appreciated.
This is the example of the process that I want to do:
A simple method is to extract the ROI using Numpy slicing, pixelate, then paste it back into the original image. I will be using the pixelation technique found in how to pixelate image using OpenCV in Python?. Here's a simple example:
Input image and ROI to be extracted
Extracted ROI
Pixelated ROI
Result
Code
import cv2
def pixelate(image):
# Get input size
height, width, _ = image.shape
# Desired "pixelated" size
h, w = (16, 16)
# Resize image to "pixelated" size
temp = cv2.resize(image, (w, h), interpolation=cv2.INTER_LINEAR)
# Initialize output image
return cv2.resize(temp, (width, height), interpolation=cv2.INTER_NEAREST)
# Load image
image = cv2.imread('1.png')
# ROI bounding box coordinates
x,y,w,h = 122,98,283,240
# Extract ROI
ROI = image[y:y+h, x:x+w]
# Pixelate ROI
pixelated_ROI = pixelate(ROI)
# Paste pixelated ROI back into original image
image[y:y+h, x:x+w] = pixelated_ROI
cv2.imshow('pixelated_ROI', pixelated_ROI)
cv2.imshow('image', image)
cv2.waitKey()
Note: The ROI bounding box coordinates were found by using the script in how to get ROI Bounding Box Coordinates without Guess & Check. For your case, I will assume that you already have the x,y,w,h bounding box coordinates obtained by cv2.boundingRect.
How can I apply mask to a color image in latest python binding (cv2)? In previous python binding the simplest way was to use cv.Copy e.g.
cv.Copy(dst, src, mask)
But this function is not available in cv2 binding. Is there any workaround without using boilerplate code?
Here, you could use cv2.bitwise_and function if you already have the mask image.
For check the below code:
img = cv2.imread('lena.jpg')
mask = cv2.imread('mask.png',0)
res = cv2.bitwise_and(img,img,mask = mask)
The output will be as follows for a lena image, and for rectangular mask.
Well, here is a solution if you want the background to be other than a solid black color. We only need to invert the mask and apply it in a background image of the same size and then combine both background and foreground. A pro of this solution is that the background could be anything (even other image).
This example is modified from Hough Circle Transform. First image is the OpenCV logo, second the original mask, third the background + foreground combined.
# http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghcircles/py_houghcircles.html
import cv2
import numpy as np
# load the image
img = cv2.imread('E:\\FOTOS\\opencv\\opencv_logo.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# detect circles
gray = cv2.medianBlur(cv2.cvtColor(img, cv2.COLOR_RGB2GRAY), 5)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=50, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
# draw mask
mask = np.full((img.shape[0], img.shape[1]), 0, dtype=np.uint8) # mask is only
for i in circles[0, :]:
cv2.circle(mask, (i[0], i[1]), i[2], (255, 255, 255), -1)
# get first masked value (foreground)
fg = cv2.bitwise_or(img, img, mask=mask)
# get second masked value (background) mask must be inverted
mask = cv2.bitwise_not(mask)
background = np.full(img.shape, 255, dtype=np.uint8)
bk = cv2.bitwise_or(background, background, mask=mask)
# combine foreground+background
final = cv2.bitwise_or(fg, bk)
Note: It is better to use the opencv methods because they are optimized.
import cv2 as cv
im_color = cv.imread("lena.png", cv.IMREAD_COLOR)
im_gray = cv.cvtColor(im_color, cv.COLOR_BGR2GRAY)
At this point you have a color and a gray image. We are dealing with 8-bit, uint8 images here. That means the images can have pixel values in the range of [0, 255] and the values have to be integers.
Let's do a binary thresholding operation. It creates a black and white masked image. The black regions have value 0 and the white regions 255
_, mask = cv.threshold(im_gray, thresh=180, maxval=255, type=cv.THRESH_BINARY)
im_thresh_gray = cv.bitwise_and(im_gray, mask)
The mask can be seen below on the left. The image on its right is the result of applying bitwise_and operation between the gray image and the mask. What happened is, the spatial locations where the mask had a pixel value zero (black), became pixel value zero in the result image. The locations where the mask had pixel value 255 (white), the resulting image retained its original gray value.
To apply this mask to our original color image, we need to convert the mask into a 3 channel image as the original color image is a 3 channel image.
mask3 = cv.cvtColor(mask, cv.COLOR_GRAY2BGR) # 3 channel mask
Then, we can apply this 3 channel mask to our color image using the same bitwise_and function.
im_thresh_color = cv.bitwise_and(im_color, mask3)
mask3 from the code is the image below on the left, and im_thresh_color is on its right.
You can plot the results and see for yourself.
cv.imshow("original image", im_color)
cv.imshow("binary mask", mask)
cv.imshow("3 channel mask", mask3)
cv.imshow("im_thresh_gray", im_thresh_gray)
cv.imshow("im_thresh_color", im_thresh_color)
cv.waitKey(0)
The original image is lenacolor.png that I found here.
Answer given by Abid Rahman K is not completely correct. I also tried it and found very helpful but got stuck.
This is how I copy image with a given mask.
x, y = np.where(mask!=0)
pts = zip(x, y)
# Assuming dst and src are of same sizes
for pt in pts:
dst[pt] = src[pt]
This is a bit slow but gives correct results.
EDIT:
Pythonic way.
idx = (mask!=0)
dst[idx] = src[idx]
The other methods described assume a binary mask. If you want to use a real-valued single-channel grayscale image as a mask (e.g. from an alpha channel), you can expand it to three channels and then use it for interpolation:
assert len(mask.shape) == 2 and issubclass(mask.dtype.type, np.floating)
assert len(foreground_rgb.shape) == 3
assert len(background_rgb.shape) == 3
alpha3 = np.stack([mask]*3, axis=2)
blended = alpha3 * foreground_rgb + (1. - alpha3) * background_rgb
Note that mask needs to be in range 0..1 for the operation to succeed. It is also assumed that 1.0 encodes keeping the foreground only, while 0.0 means keeping only the background.
If the mask may have the shape (h, w, 1), this helps:
alpha3 = np.squeeze(np.stack([np.atleast_3d(mask)]*3, axis=2))
Here np.atleast_3d(mask) makes the mask (h, w, 1) if it is (h, w) and np.squeeze(...) reshapes the result from (h, w, 3, 1) to (h, w, 3).
I am using Python and OpenCV. I am now using grabcut() to crop out the object I want. Here is my code:
img = cv2.imread('test.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (2,2,630,930)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0), 0,1).astype('uint8')
img = img*mask2[:,:, np.newaxis]
Afterwards, I try to find out the contour.
I have tried to find the contour by the code below:
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
And it returns a contours array with length 48. When I draw this out:
First question is how can I get the contour (array) of this grab cut?
Second question: as you can see, the background color is black. How can I change the background color to white?
Thank you.
At first, you need to get the background. To this must be subtracted from the original image with the mask image. And then change the black background to white (or any color). And then back to add with the image of the mask.
import numpy as np
import cv2
cv2.namedWindow(‘image’, cv2.WINDOW_NORMAL)
#Load the Image
imgo = cv2.imread(‘input.jpg’)
height, width = imgo.shape[:2]
#Create a mask holder
mask = np.zeros(imgo.shape[:2],np.uint8)
#Grab Cut the object
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
#Hard Coding the Rect… The object must lie within this rect.
rect = (10,10,width-30,height-30)
cv2.grabCut(imgo,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask = np.where((mask==2)|(mask==0),0,1).astype(‘uint8’)
img1 = imgo*mask[:,:,np.newaxis]
#Get the background
background = imgo – img1
#Change all pixels in the background that are not black to white
background[np.where((background > [0,0,0]).all(axis = 2))] =[255,255,255]
#Add the background and the image
final = background + img1
#To be done – Smoothening the edges….
cv2.imshow(‘image’, final )
k = cv2.waitKey(0)
if k==27:
cv2.destroyAllWindows()
Information taken from the site
https://nxtify.wordpress.com/2015/02/24/image-background-removal-using-opencv-in-python/
If you want a single contour like boundary, you can go for edge detection on the output of grabcut and morphology dilation on the edge image to get as a proper connected contour and can get the array of pixels of the boundary.
For making the background white, All the pixels outside your bounding box can be default made to white. The black pixels inside your bounding box, you can compare with the original image corresponding gray level, if it is black, you can keep it, otherwise make it to white. Because if the original pixel is not black, but made black by grabcut, then it is considered as background. If black pixel is there in foreground, the grabcut never makes it to black (ideal case).