Add blur/gradient to image outside of mask in python - python

I have a bounding box whose coordinates are given by (x, y, w, h) where x and y are top-left coordinates of the box. I'd like to apply a blur or gradient outside the box. How would I create a mask using the coordinates above and apply this effect outside the mask with either PIL or cv2 similar to the image below?

Here is an example, I think that should be helpful to adapt it to your program.
import cv2
original = cv2.imread("spidy.png", 3)
blurred = cv2.GaussianBlur(original, (25,25), 0)
original[0:500, 0:500] = blurred[0:500, 0:500]
cv2.imwrite('cvBlurredOutput.jpg', original)
1) First read Image
2) Blur it, the parameter (25,25) is the blur kernel, basically the width/height of your "blur Brush"
3) finally copy the region of interest from the blurred to the original

Related

how to draw thousand of circles at once openCv faster - (Maybe use GPU)

I need to draw thousands of dots on a given area of an image (frame of a video).
Using a loop is the easiest way to do this.
while i < num:
x = random.randint(min_x, max_x)
y = random.randint(min_y, max_y)
//this if is to check the the random points are within the original shape
if cv2.pointPolygonTest(contour, (int(x), int(y)), False)==1:
cv2.circle(img, (int(x), int(y)), 2, color, -1)
i = i+1;
this process takes a long time to complete.
how can I achieve this more efficiently?
Maybe it's possible to speed it up using several tricks:
Try to get rid of the loop and vectorize your operations.
You can vectorize the (x, y) point generation by passing size to random.randint. If after filtering they don't amount to num, you can generate another set.
Instead of pointPolygonTest, maybe you can try matplotlib.path.Path.contains_points that operates on a vector of points rather than a single point.
For the circle, once you filter all your circle centers, create an all zeros image to draw your circles, then mark centers in this image (again, vectorize). If you want color circles you'll have to set the pixel value to appropriate gray level for each channel. Then dilate using a circular structuring element having the required radius. For small radii, these circles should be fine. Or, you can try Gaussian blur as well, because, if you convolve a Gaussian with an impulse, it'll give you a Gaussian, and a symmetric Gaussian will resemble a circle in the image. You can also use filter2D to do this if your circles have the same size. If the dilation result isn't good, you can create your own kernel resembling the circle you want, then convolve it with centers image.
Copy all non-zero pixels from this circles image to your img using the circles image as a mask.
A simple example for creating circles:
import numpy as np
import cv2 as cv
# create random centers for circles
img = np.random.randint(low=0, high=1000, size=(256, 256))
img = np.uint8(img < 1) * 255
# use Gaussian bluer to create cricles
img1 = cv.GaussianBlur(img, (9, 9), 3)*20
# use morphological dilation bluer to create cricles
se = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5))
img2 = cv.dilate(img, se)
Centers:
Circles using dilation:
Circles using Gaussian blur:

Cut all except region of interest python

i'm totally new to this kind of things, i used SLIC to get superpixels from an image, now i have extracted the single superpixel detected but it's like the whole start img dimension except that there is the superpixel and the rest of the image is black, i'm sorry for my bad english, i'll try to explain below.
import cv2
import numpy as np
from skimage.segmentation import slic
myimg = cv2.imread('4.5.jpg')
segments = slic(myimg, n_segments=200, compactness=10, sigma=1)
for i, segVal in enumerate(np.unique(segments)):
mask = np.zeros(myimg.shape[:2], dtype = "uint8")
mask[segments == segVal] = 255
cv2.imwrite('output.png', cv2.bitwise_and(myimg, myimg, mask = mask))
#show the masked region
#cv2.imshow("Mask", mask)
cv2.imshow("Applied", cv2.bitwise_and(myimg, myimg, mask = mask))
cv2.waitKey(1)
that's actually my code to get superpixels, but when i store the single superpixel what i get is in that link (i'm not allowed yet to embed images):
superpixel
now as u can see there is a big black region with the H and W of the original image and the superpixel, i wish to crop only a "rectangle or square" with the superpixel region, how can i do that? thank you and sorry for my english
For this task you can use cv2.findContours. Refer to its documentation to know how to use it. After finding out the contours which will just be one in your case you can use
x,y,w,h = cv2.boundingRect(cnt)
where x, y are the coordinates of top left corner and w, h are the width and height of the rectangle. Now we can know all the points of the required recatngle and you can crop it using numpy indexing.

Bulk removing unwanted parts of images

I have downloaded a number of images (1000) from a website but they each have a black and white ruler running along 1 or 2 edges and some have these catalogue number tickets. I need these elements removed, the ruler at the very least.
Example images of coins:
The images all have the ruler in slightly different places so i cant just preform the same crop on them.
So I tried to remove the black and replace it with white using this code
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
im = Image.open('image-0.jpg')
im = im.convert('RGBA')
data = np.array(im) # "data" is a height x width x 4 numpy array
red, green, blue, alpha = data.T # Temporarily unpack the bands for readability
# Replace black with white
black_areas = (red < 150) & (blue < 150) & (green < 150)
data[..., :-1][black_areas.T] = (255, 255, 255) # Transpose back needed
im2 = Image.fromarray(data)
im2.show()
but it pretty much just removed half the coin as well:
I was having a read of some posts on opencv but though I'd see if there was a simpler way I'd missed first.
So I have taken a look at your problem and I have found a solution for your two images you provided, I hope it works for you other images as well but it is always hard to tell as it can be different on an individual basis. This solution is using OpenCV for preprocessing and contour detection to get the 2nd and 3rd largest elements in your picture (largest is the bounding box around the edges) which should be your coins. Then I create a box around those two items and add some padding before I crop to size.
So we start off with preprocessing:
import numpy as np
import cv2
img = cv2.imread(r'<PATH TO YOUR IMAGE>')
img = cv2.resize(img, None, fx=3, fy=3)
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(imgray, (5, 5), 0)
ret, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Still rather basic, we make the image bigger so it is easier to detect contours, then we turn it into grayscale, blur it and apply thresholding to it so we turn all grey values either white or black. This then gives us the following image:
We now do contour detection, get the areas around our contours and sort them by the biggest area. Then we drop the biggest one as it is the box around the whole image and take the 2nd and 3rd biggest. And then get the x,y,w,h values we are interested in.
contours, hierarchy = cv2.findContours(
thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
areas = []
for cnt in contours:
area = cv2.contourArea(cnt)
areas.append((area, cnt))
areas.sort(key=lambda x: x[0], reverse=True)
areas.pop(0)
x, y, w, h = cv2.boundingRect(areas[0][1])
x2, y2, w2, h2 = cv2.boundingRect(areas[1][1])
If we draw a rectangle around those contours:
Now we take those coordinates and create a box around both of them. This might need some minor adjusting as I just quickly took the bigger width of the two and not the corresponding one for the right coin but since I added extra padding it should be fine in most cases. And finally crop to size:
pad = 15
img = img[(min(y, y2) - pad) : (max(y, y2) + max(h, h2) + pad),
(min(x, x2) - pad) : (max(x, x2) + max(w, w2) + pad)]
I hope this helps you to understand how you could achieve what you want, I tried it on both your images and it worked well for them. It might need some adjustments and depending on how your other images look the simple approach of taking the two biggest objects (apart from image bounding box) might be turned into something more sophisticated to detect the cricular shapes or something along those lines. Alternatively you could try to detect the rulers and crop from their position inwards. You will have to decide after you have done this on more example images in your dataset.
If you're looking for a robust solution, you should try something like Max Kaha's response, since it'll provide you with greater fine tuning.
Since the rulers tend to be left with just a little bit of text after your "black to white" filter, a quick solution is to use erosion followed by a dilation to create a mask for your images, and then apply the mask to the original image.
Pillow offers that with the ImageFilter class. Here's your code with a few modifications that'll achieve that:
from PIL import Image, ImageFilter
import numpy as np
import matplotlib.pyplot as plt
WHITE = 255, 255, 255
input_image = Image.open('image.png')
input_image = input_image.convert('RGBA')
input_data = np.array(input_image) # "data" is a height x width x 4 numpy array
red, green, blue, alpha = input_data.T # Temporarily unpack the bands for readability
# Replace black with white
thresh = 30
black_areas = (red < thresh) & (blue < thresh) & (green < thresh)
input_data[..., :-1][black_areas.T] = WHITE # Transpose back needed
erosion_factor = 5
# dilation is bigger to avoid cropping the objects of interest
dilation_factor = 11
erosion_filter = ImageFilter.MaxFilter(erosion_factor)
dilation_filter = ImageFilter.MinFilter(dilation_factor)
eroded = Image.fromarray(input_data).filter(erosion_filter)
dilated = eroded.filter(dilation_filter)
mask_threshold = 220
# the mask is black on regions to be hidden
mask = dilated.convert('L').point(lambda x: 255 if x < mask_threshold else 0)
# create base image
output_image = Image.new('RGBA', input_image.size, WHITE)
# paste only the desired regions
output_image.paste(input_image, mask=mask)
output_image.show()
You should also play around with the black to white threshold and the erosion/dilation factors to try and find the best fit for most of your images.

Cut out a piece from image the piece is not rectangular (eg trapeze), and turn it into a rectangle that fits with the board

I will bring an example I have a picture of a swimming pool with some tracks I want to take only the three middle tracks Now what is the best way to cut the image in a trapeze shape then how to take this trapeze and try to fit it to the size of the window that will have a relatively similar ratio between the two sides (upper and lower)
image for the example
I modified this example
Result:
import numpy as np
import cv2
# load image
img = cv2.imread('pool.jpg')
# resize to easily fit on screen
img = cv2.resize(img,None,fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
# determine cornerpoints of the region of interest
pts1 = np.float32([[400,30],[620,30],[50,700],[1000,700]])
# provide new coordinates of cornerpoints
pts2 = np.float32([[0,0],[300,0],[0,600],[300,600]])
# determine transformationmatrix
M = cv2.getPerspectiveTransform(pts1,pts2)
# apply transformationmatrix
dst = cv2.warpPerspective(img,M,(300,600))
# display image
cv2.imshow("img", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
Note the rezise function, you may wish to delete that line, but you will have to change the coordinates of the cornerpoints accordingly.
I used about the height and width of the base of the trapezoid for the new image (300,600).
You can tweak the cornerpoints and final image size as you see fit.
You can use imutils.four_point_transform function. You can read more about it here.
Basic usage is finding the document contours on a canny edge detected image (again, you can use imutils package that I linked), find the contours on that image and then apply four_point_transform on that contour.
EDIT: How to use canny edge detection and four_point_transform
For finding contours you can use openCV and imutils like this:
cnts = cv2.findContours(edged_image.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
Now, when you have the contours just iterate through and see which one is the biggest and has four points (4 vertices). Then just pass the image and the contour to the four_point_transform function like this:
image_2 = four_point_transform(image, biggest_contour)
That's it.

How to copy a cropped image onto the original one, given the coordinates of the center of the crop

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)

Categories

Resources