Is there a way to cut out non rectangular areas of an image with Python PIL?
e.g. in this picture I want to exclude all black areas as well as towers, rooftops and poles.
http://img153.imageshack.us/img153/5330/skybig.jpg
I guess the ImagePath Module can do that, but furthermore, how can I read data of e.g. a svg file and convert it into a path?
Any help will be appreciated.
(My sub question is presumably the easier task: how to cut at least a circle of an image?)
If I understood correctly, you want to make some areas transparent within the image. And these areas are random shaped. Easiest way (that I can think of) is to create a mask and put it to the alpha channel of the image. Below is a code that shows how to do this.
If your question was "How to create a polygon mask" I will redirect you to:
SciPy Create 2D Polygon Mask
and look the accepted answer.
br,
Juha
import numpy
import Image
# read image as RGB and add alpha (transparency)
im = Image.open("lena.png").convert("RGBA")
# convert to numpy (for convenience)
imArray = numpy.asarray(im)
# create mask (zeros + circle with ones)
center = (200,200)
radius = 100
mask = numpy.zeros((imArray.shape[0],imArray.shape[1]))
for i in range(imArray.shape[0]):
for j in range(imArray.shape[1]):
if (i-center[0])**2 + (j-center[0])**2 < radius**2:
mask[i,j] = 1
# assemble new image (uint8: 0-255)
newImArray = numpy.empty(imArray.shape,dtype='uint8')
# colors (three first columns, RGB)
newImArray[:,:,:3] = imArray[:,:,:3]
# transparency (4th column)
newImArray[:,:,3] = mask*255
# back to Image from numpy
newIm = Image.fromarray(newImArray, "RGBA")
newIm.save("lena3.png")
Edit
Actually, I could not resist... the polygon mask solution was so elegant (replace the above circle with this):
# create mask
polygon = [(100,100), (200,100), (150,150)]
maskIm = Image.new('L', (imArray.shape[0], imArray.shape[1]), 0)
ImageDraw.Draw(maskIm).polygon(polygon, outline=1, fill=1)
mask = numpy.array(maskIm)
Edit2
Now when I think of it. If you have a black and white svg, you can load your svg directly as mask (assuming white is your mask). I have no sample svg images, so I cannot test this. I am not sure if PIL can open svg images.
Related
I'm really sorry for this basic question, but I'm new to OpenCV and image processing in general, and couldn't figure this out after fiddling around for a while.
Here's what I'm trying to do:
I have a transparent PNG image:
I created a binary mask out of this with the transparent region being black and the object being white:
Now, I have another image like this, having the same dimensions:
Now, I wish to superimpose the white masked part from the first image (the actual object) onto this image. How do I do this?
Here is one way of doing it:
import cv2
# Load images
bg = cv2.imread('bg.png')
obj = cv2.imread('object.png')
mask = cv2.imread('mask.png')
# Zero background where we want to overlay
bg[mask>0]=0
# Add object to zeroed out space
bg += obj*(mask>0)
cv2.imwrite('result.png',bg)
Using the fact that we have numpy arrays at hands here, we can first extend the mask to three dimensions:
# Case 1: original mask is OpenCV mask (foreground values 255)
mask = np.dstack([(mask > 0)]*3)
# Case 2: original mask is already boolean
mask = np.dstack([mask]*3)
And then copy the image over using the mask:
np.copyto(background, foreground, where=mask)
I have two images, one overlay and one background.
I want to create a new image, by editing overlay image and manipulating it to show only the pixels which have blue colour in the background image.
I dont want to add the background, it is only for selecting the pixels.
Rest part should be transparent.
Any hints or ideas please? PS: I edited result image with paint so its not perfect.
Image 1 is background image.
Image 2 is overlay image.
Image 3 is the check I want to perform. (to find out which pixels have blue in background and making remaining pixels transparent)
Image 4 is the result image after editing.
I renamed your images according to my way of thinking, so I took this as image.png:
and this as mask.png:
Then I did what I think you want as follows. I wrote it quite verbosely so you can see all the steps along the way:
#!/usr/local/bin/python3
from PIL import Image
import numpy as np
# Open input images
image = Image.open("image.png")
mask = Image.open("mask.png")
# Get dimensions
h,w=image.size
# Resize mask to match image, taking care not to introduce new colours (Image.NEAREST)
mask = mask.resize((h,w), Image.NEAREST)
mask.save('mask_resized.png')
# Convert both images to numpy equivalents
npimage = np.array(image)
npmask = np.array(mask)
# Make image transparent where mask is not blue
# Blue pixels in mask seem to show up as RGB(163 204 255)
npimage[:,:,3] = np.where((npmask[:,:,0]<170) & (npmask[:,:,1]<210) & (npmask[:,:,2]>250),255,0).astype(np.uint8)
# Identify grey pixels in image, i.e. R=G=B, and make transparent also
RequalsG=np.where(npimage[:,:,0]==npimage[:,:,1],1,0)
RequalsB=np.where(npimage[:,:,0]==npimage[:,:,2],1,0)
grey=(RequalsG*RequalsB).astype(np.uint8)
npimage[:,:,3] *= 1-grey
# Convert numpy image to PIL image and save
PILrgba=Image.fromarray(npimage)
PILrgba.save("result.png")
And this is the result:
Notes:
a) Your image already has an (unused) alpha channel present.
b) Any lines starting:
npimage[:,:,3] = ...
are just modifying the 4th channel, i.e. the alpha/transparency channel of the image
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)
I have a plot of spatial data that I display with imshow().
I need to be able to overlay the crystal lattice that produced the data. I have a png
file of the lattice that loads as a black and white image.The parts of this image I want to
overlay are the black lines that are the lattice and not see the white background between the lines.
I'm thinking that I need to set the alphas for each background ( white ) pixel to transparent (0 ? ).
I'm so new to this that I don't really know how to ask this question.
EDIT:
import matplotlib.pyplot as plt
import numpy as np
lattice = plt.imread('path')
im = plt.imshow(data[0,:,:],vmin=v_min,vmax=v_max,extent=(0,32,0,32),interpolation='nearest',cmap='jet')
im2 = plt.imshow(lattice,extent=(0,32,0,32),cmap='gray')
#thinking of making a mask for the white background
mask = np.ma.masked_where( lattice < 1,lattice ) #confusion here b/c even tho theimage is gray scale in8, 0-255, the numpy array lattice 0-1.0 floats...?
With out your data, I can't test this, but something like
import matplotlib.pyplot as plt
import numpy as np
import copy
my_cmap = copy.copy(plt.cm.get_cmap('gray')) # get a copy of the gray color map
my_cmap.set_bad(alpha=0) # set how the colormap handles 'bad' values
lattice = plt.imread('path')
im = plt.imshow(data[0,:,:],vmin=v_min,vmax=v_max,extent=(0,32,0,32),interpolation='nearest',cmap='jet')
lattice[lattice< thresh] = np.nan # insert 'bad' values into your lattice (the white)
im2 = plt.imshow(lattice,extent=(0,32,0,32),cmap=my_cmap)
Alternately, you can hand imshow a NxMx4 np.array of RBGA values, that way you don't have to muck with the color map
im2 = np.zeros(lattice.shape + (4,))
im2[:, :, 3] = lattice # assuming lattice is already a bool array
imshow(im2)
The easy way is to simply use your image as a background rather than an overlay. Other than that you will need to use PIL or Python Image Magic bindings to convert the selected colour to transparent.
Don't forget you will probably also need to resize either your plot or your image so that they match in size.
Update:
If you follow the tutorial here with your image and then plot your data over it you should get what you need, note that the tutorial uses PIL so you will need that installed as well.
How do you draw semi-transparent polygons using the Python Imaging Library?
Can you draw the polygon on a separate RGBA image then use the Image.paste(image, box, mask) method?
Edit: This works.
from PIL import Image
from PIL import ImageDraw
back = Image.new('RGBA', (512,512), (255,0,0,0))
poly = Image.new('RGBA', (512,512))
pdraw = ImageDraw.Draw(poly)
pdraw.polygon([(128,128),(384,384),(128,384),(384,128)],
fill=(255,255,255,127),outline=(255,255,255,255))
back.paste(poly,mask=poly)
back.show()
http://effbot.org/imagingbook/image.htm#image-paste-method
I think #Nick T's answer is good, but you need to be careful when using his code as written with a very large background image, especially in the case that you may be annotating several polygons on said image. This is something I do when processing huge satellite images with some object detection code and annotating the detections using a transparent rectangle. To make the code efficient no matter the size of the background image, I make the following suggestion.
I would modify the solution to specify that the polygon image that you will paste be only as large as required to hold the polygon, not the same size as the back image. The coordinates of the polygon are specified with respect to the local bounding box, not the global image coordinates. Then you paste the polygon image at the offset in the larger background image.
import Image
import ImageDraw
img_size = (512,512)
poly_size = (256,256)
poly_offset = (128,128) #location in larger image
back = Image.new('RGBA', img_size, (255,0,0,0) )
poly = Image.new('RGBA', poly_size )
pdraw = ImageDraw.Draw(poly)
pdraw.polygon([ (0,0), (256,256), (0,256), (256,0)],
fill=(255,255,255,127), outline=(255,255,255,255))
back.paste(poly, poly_offset, mask=poly)
back.show()
Using the Image.paste(image, box, mask) method will convert the alpha channel in the pasted area of the background image into the corresponding transparency value of the polygon image.
The Image.alpha_composite(im1,im2) method utilizes the alpha channel of the "pasted" image, and will not turn the background transparent. However, this method again needs two equally sized images.