Related
I am trying to create an image with the edges replaced with text, similar to This Youtube video thumbnail but from a source image. I've used OpenCV to get a version of a source image with edges, and Pillow to actually write the text, but I'm not sure where to start when it comes to actually manipulating the text automatically to fit to the edges. The code I have so far is:
import cv2 as cv
from matplotlib import pyplot as plt
from PIL import Image, ImageFont, ImageDraw, ImageShow
font = ImageFont.truetype(r"C:\Users\X\Downloads\Montserrat\Montserrat-Light.ttf", 12)
text = ["text", "other text"]
img = cv.imread(r"C:\Users\X\Pictures\picture.jpg",0)
edges = cv.Canny(img,100,200)
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
im_pil = Image.fromarray(edges)
This code is just for the edge detection and moving the detected edges to Pillow.
Please help
I am not sure where the "edges" comes in from the canny edge detector.
However, the circular text wrap can be done very simply in Python/Wand that uses ImageMagick. Or one can do that in Python/OpenCV using cv2.remap and custom transformation maps.
Input:
1. Python Wand
(output size determined automatically from input size)
from wand.image import Image
from wand.font import Font
from wand.display import display
with Image(filename='some_text.png') as img:
img.background_color = 'white'
img.virtual_pixel = 'white'
# 360 degree arc, rotated 0 degrees
img.distort('arc', (360,0))
img.save(filename='some_text_arc.png')
img.format = 'png'
display(img)
Result:
2. Python/OpenCV
import numpy as np
import cv2
import math
# read input
img = cv2.imread("some_text.png")
hin, win = img.shape[:2]
win2 = win / 2
# specify desired square output dimensions and center
hout = 100
wout = 100
xcent = wout / 2
ycent = hout / 2
hwout = max(hout,wout)
hwout2 = hwout / 2
# set up the x and y maps as float32
map_x = np.zeros((hout, wout), np.float32)
map_y = np.zeros((hout, wout), np.float32)
# create map with the arc distortion formula --- angle and radius
for y in range(hout):
Y = (y - ycent)
for x in range(wout):
X = (x - xcent)
XX = (math.atan2(Y,X)+math.pi/2)/(2*math.pi)
XX = XX - int(XX+0.5)
XX = XX * win + win2
map_x[y, x] = XX
map_y[y, x] = hwout2 - math.hypot(X,Y)
# do the remap this is where the magic happens
result = cv2.remap(img, map_x, map_y, cv2.INTER_CUBIC, borderMode = cv2.BORDER_CONSTANT, borderValue=(255,255,255))
# save results
cv2.imwrite("some_text_arc.jpg", result)
# display images
cv2.imshow('img', img)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Neither OpenCV nor PIL has a way to do that, but you can use ImageMagick.
How to warp an image to take shape of path with python?
I'm trying to add a random noise from uniform distribution between min pixel
value and 0.1 times the maximum pixel value to each pixel for each channel of original image.
Here's my code so far:
[in]:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Read image with cv2
image = cv2.imread('example_image.jpg' , 1)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# Display image
imshow(image_rgb)
# R,G,B channel separation
R, G, B = cv2.split(image_rgb)
# Creating Noise
noise_R = np.random.uniform(R.min(),R.max()*0.1, R.size)
noise_R.shape = (256,256)
noise_G = np.random.uniform(B.min(),B.max()*0.1, G.size)
noise_G.shape = (256,256)
noise_B = np.random.uniform(G.min(), G.max()*0.1, B.size)
noise_B.shape = (256,256)
# Adding noise to each channel separately
R = R + noise_R
G = G + noise_G
B = B + noise_B
rgb_noise = R + G + B
noisy_image = image + rgb_noise
[out]:
ValueError: operands could not be broadcast together with shapes (256,256,3) (256,256)
I'm getting an ValueError that the array shapes for rgb_noise and image are not equal. I've tried changing the shape of rgb_noise to that of image's but the I get a size error. How to fix it ? Is there any better method ?
Your solution is a bit verbose, and could be made more compact.
However, the reason why you do not get white-ish noise is that you compute your red channel differently from the other two.
Changing this:
noise_R = np.random.uniform(R_min,R_max*0.3, image_G.size)
to this:
noise_R = np.random.uniform(R_min,R_max*0.1, image_R.size)
You can be simplistic and add the noise by only the numpy array.
import numpy
import matplotlib.pyplot as plt
import cv2
Look, plotting the image will only work good with jupyter notebooks.
Do cv2.imshow() for other IDEs.
1) Have your Image
img = cv2.imread('path').astype(np.uint0)
2) Make a random noise
r, g, b = img.shape
noise = np.random.randint(0,255,r*g*b).reshape(r,g,b)
3) Blend them
image_with_noise = cv2.addWeighted(img,0.5,noise,0.5,0)
You can adjust the value of alpha and beta values.
There you have a noisy image!
I'm trying to convert an image from a numpy array format to a PIL one. This is my code:
img = numpy.array(image)
row,col,ch= np.array(img).shape
mean = 0
# var = 0.1
# sigma = var**0.5
gauss = np.random.normal(mean,1,(row,col,ch))
gauss = gauss.reshape(row,col,ch)
noisy = img + gauss
im = Image.fromarray(noisy)
The input to this method is a PIL image. This method should add Gaussian noise to the image and return it as a PIL image once more.
Any help is greatly appreciated!
In my comments I meant that you do something like this:
import numpy as np
from PIL import Image
img = np.array(image)
mean = 0
# var = 0.1
# sigma = var**0.5
gauss = np.random.normal(mean, 1, img.shape)
# normalize image to range [0,255]
noisy = img + gauss
minv = np.amin(noisy)
maxv = np.amax(noisy)
noisy = (255 * (noisy - minv) / (maxv - minv)).astype(np.uint8)
im = Image.fromarray(noisy)
Can anyone help me figure out what's happening in my image auto-cropping script? I have a png image with a large transparent area/space. I would like to be able to automatically crop that space out and leave the essentials. Original image has a squared canvas, optimally it would be rectangular, encapsulating just the molecule.
here's the original image:
Doing some googling i came across PIL/python code that was reported to work, however in my hands, running the code below over-crops the image.
import Image
import sys
image=Image.open('L_2d.png')
image.load()
imageSize = image.size
imageBox = image.getbbox()
imageComponents = image.split()
rgbImage = Image.new("RGB", imageSize, (0,0,0))
rgbImage.paste(image, mask=imageComponents[3])
croppedBox = rgbImage.getbbox()
print imageBox
print croppedBox
if imageBox != croppedBox:
cropped=image.crop(croppedBox)
print 'L_2d.png:', "Size:", imageSize, "New Size:",croppedBox
cropped.save('L_2d_cropped.png')
the output is this:
Can anyone more familiar with image-processing/PLI can help me figure out the issue?
Install Pillow
pip install Pillow
and use as
from PIL import Image
image=Image.open('L_2d.png')
imageBox = image.getbbox()
cropped = image.crop(imageBox)
cropped.save('L_2d_cropped.png')
When you search for boundaries by mask=imageComponents[3], you search only by blue channel.
You can use numpy, convert the image to array, find all non-empty columns and rows and then create an image from these:
import Image
import numpy as np
image=Image.open('L_2d.png')
image.load()
image_data = np.asarray(image)
image_data_bw = image_data.max(axis=2)
non_empty_columns = np.where(image_data_bw.max(axis=0)>0)[0]
non_empty_rows = np.where(image_data_bw.max(axis=1)>0)[0]
cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns))
image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
new_image = Image.fromarray(image_data_new)
new_image.save('L_2d_cropped.png')
The result looks like
If anything is unclear, just ask.
I tested most of the answers replied in this post, however, I was ended up my own answer. I used anaconda python3.
from PIL import Image, ImageChops
def trim(im):
bg = Image.new(im.mode, im.size, im.getpixel((0,0)))
diff = ImageChops.difference(im, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
#Bounding box given as a 4-tuple defining the left, upper, right, and lower pixel coordinates.
#If the image is completely empty, this method returns None.
bbox = diff.getbbox()
if bbox:
return im.crop(bbox)
if __name__ == "__main__":
bg = Image.open("test.jpg") # The image to be cropped
new_im = trim(bg)
new_im.show()
Here's another version using pyvips.
import sys
import pyvips
image = pyvips.Image.new_from_file(sys.argv[1])
left, top, width, height = image.find_trim(threshold=2, background=[255, 255, 255])
image = image.crop(left, top, width, height)
image.write_to_file(sys.argv[2])
The pyvips trimmer is useful for photographic images. It does a median filter, subtracts the background, finds pixels over the threshold, and removes up to the first and last row and column outside this set. The median and threshold mean it is not thrown off by things like JPEG compression, where noise or invisible compression artefacts can confuse other trimmers.
If you don't supply the background argument, it uses the pixel at (0, 0). threshold defaults to 10, which is about right for JPEG.
Here it is running on an 8k x 8k pixel NASA earth image:
$ time ./trim.py /data/john/pics/city_lights_asia_night_8k.jpg x.jpg
real 0m1.868s
user 0m13.204s
sys 0m0.280s
peak memory: 100mb
Before:
After:
There's a blog post with some more discussion here.
This is an improvement over snew's reply, which works for transparent background. With mathematical morphology we can make it work on white background (instead of transparent), with the following code:
from PIL import Image
from skimage.io import imread
from skimage.morphology import convex_hull_image
from skimage.color import rgb2gray
im = imread('L_2d.jpg')
plt.imshow(im)
plt.title('input image')
plt.show()
# create a binary image
im1 = 1 - rgb2gray(im)
threshold = 0.5
im1[im1 <= threshold] = 0
im1[im1 > threshold] = 1
chull = convex_hull_image(im1)
plt.imshow(chull)
plt.title('convex hull in the binary image')
plt.show()
imageBox = Image.fromarray((chull*255).astype(np.uint8)).getbbox()
cropped = Image.fromarray(im).crop(imageBox)
cropped.save('L_2d_cropped.jpg')
plt.imshow(cropped)
plt.show()
pilkit already contains processor for automatic cropping TrimBorderColor. SOmething like this should work:
from pilkit.lib import Image
from pilkit.processors import TrimBorderColor
img = Image.open('/path/to/my/image.png')
processor = TrimBorderColor()
new_img = processor.process(img)
https://github.com/matthewwithanm/pilkit/blob/b24990167aacbaab3db6d8ec9a02f9ad42856898/pilkit/processors/crop.py#L33
Came across this post recently and noticed the PIL library has changed. I re-implemented this with openCV:
import cv2
def crop_im(im, padding=0.1):
"""
Takes cv2 image, im, and padding % as a float, padding,
and returns cropped image.
"""
bw = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
rows, cols = bw.shape
non_empty_columns = np.where(bw.min(axis=0)<255)[0]
non_empty_rows = np.where(bw.min(axis=1)<255)[0]
cropBox = (int(min(non_empty_rows) * (1 - padding)),
int(min(max(non_empty_rows) * (1 + padding), rows)),
int(min(non_empty_columns) * (1 - padding)),
int(min(max(non_empty_columns) * (1 + padding), cols)))
cropped = im[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
return cropped
im = cv2.imread('testimage.png')
cropped = crop_im(im)
cv2.imshow('', cropped)
cv2.waitKey(0)
I know that this post is old but, for some reason, none of the suggested answers worked for me. So I hacked my own version from existing answers:
import Image
import numpy as np
import glob
import shutil
import os
grey_tolerance = 0.7 # (0,1) = crop (more,less)
f = 'test_image.png'
file,ext = os.path.splitext(f)
def get_cropped_line(non_empty_elms,tolerance,S):
if (sum(non_empty_elms) == 0):
cropBox = ()
else:
non_empty_min = non_empty_elms.argmax()
non_empty_max = S - non_empty_elms[::-1].argmax()+1
cropBox = (non_empty_min,non_empty_max)
return cropBox
def get_cropped_area(image_bw,tol):
max_val = image_bw.max()
tolerance = max_val*tol
non_empty_elms = (image_bw<=tolerance).astype(int)
S = non_empty_elms.shape
# Traverse rows
cropBox = [get_cropped_line(non_empty_elms[k,:],tolerance,S[1]) for k in range(0,S[0])]
cropBox = filter(None, cropBox)
xmin = [k[0] for k in cropBox]
xmax = [k[1] for k in cropBox]
# Traverse cols
cropBox = [get_cropped_line(non_empty_elms[:,k],tolerance,S[0]) for k in range(0,S[1])]
cropBox = filter(None, cropBox)
ymin = [k[0] for k in cropBox]
ymax = [k[1] for k in cropBox]
xmin = min(xmin)
xmax = max(xmax)
ymin = min(ymin)
ymax = max(ymax)
ymax = ymax-1 # Not sure why this is necessary, but it seems to be.
cropBox = (ymin, ymax-ymin, xmin, xmax-xmin)
return cropBox
def auto_crop(f,ext):
image=Image.open(f)
image.load()
image_data = np.asarray(image)
image_data_bw = image_data[:,:,0]+image_data[:,:,1]+image_data[:,:,2]
cropBox = get_cropped_area(image_data_bw,grey_tolerance)
image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
new_image = Image.fromarray(image_data_new)
f_new = f.replace(ext,'')+'_cropped'+ext
new_image.save(f_new)
I'm working on a little problem in my sparetime involving analysis of some images obtained through a microscope. It is a wafer with some stuff here and there, and ultimately I want to make a program to detect when certain materials show up.
Anyways, first step is to normalize the intensity across the image, since the lens does not give uniform lightning. Currently I use an image, with no stuff on, only the substrate, as a background, or reference, image. I find the maximum of the three (intensity) values for RGB.
from PIL import Image
from PIL import ImageDraw
rmax = 0;gmax = 0;bmax = 0;rmin = 300;gmin = 300;bmin = 300
im_old = Image.open("test_image.png")
im_back = Image.open("background.png")
maxx = im_old.size[0] #Import the size of the image
maxy = im_old.size[1]
im_new = Image.new("RGB", (maxx,maxy))
pixback = im_back.load()
for x in range(maxx):
for y in range(maxy):
if pixback[x,y][0] > rmax:
rmax = pixback[x,y][0]
if pixback[x,y][1] > gmax:
gmax = pixback[x,y][1]
if pixback[x,y][2] > bmax:
bmax = pixback[x,y][2]
pixnew = im_new.load()
pixold = im_old.load()
for x in range(maxx):
for y in range(maxy):
r = float(pixold[x,y][0]) / ( float(pixback[x,y][0])*rmax )
g = float(pixold[x,y][1]) / ( float(pixback[x,y][1])*gmax )
b = float(pixold[x,y][2]) / ( float(pixback[x,y][2])*bmax )
pixnew[x,y] = (r,g,b)
The first part of the code determines the maximum intensity of the RED, GREEN and BLUE channels, pixel by pixel, of the background image, but needs only be done once.
The second part takes the "real" image (with stuff on it), and normalizes the RED, GREEN and BLUE channels, pixel by pixel, according to the background. This takes some time, 5-10 seconds for an 1280x960 image, which is way too slow if I need to do this to several images.
What can I do to improve the speed? I thought of moving all the images to numpy arrays, but I can't seem to find a fast way to do that for RGB images.
I'd rather not move away from python, since my C++ is quite low-level, and getting a working FORTRAN code would probably take longer than I could ever save in terms of speed :P
import numpy as np
from PIL import Image
def normalize(arr):
"""
Linear normalization
http://en.wikipedia.org/wiki/Normalization_%28image_processing%29
"""
arr = arr.astype('float')
# Do not touch the alpha channel
for i in range(3):
minval = arr[...,i].min()
maxval = arr[...,i].max()
if minval != maxval:
arr[...,i] -= minval
arr[...,i] *= (255.0/(maxval-minval))
return arr
def demo_normalize():
img = Image.open(FILENAME).convert('RGBA')
arr = np.array(img)
new_img = Image.fromarray(normalize(arr).astype('uint8'),'RGBA')
new_img.save('/tmp/normalized.png')
See http://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.fromimage.html#scipy.misc.fromimage
You can say
databack = scipy.misc.fromimage(pixback)
rmax = numpy.max(databack[:,:,0])
gmax = numpy.max(databack[:,:,1])
bmax = numpy.max(databack[:,:,2])
which should be much faster than looping over all (r,g,b) triplets of your image.
Then you can do
dataold = scip.misc.fromimage(pixold)
r = dataold[:,:,0] / (pixback[:,:,0] * rmax )
g = dataold[:,:,1] / (pixback[:,:,1] * gmax )
b = dataold[:,:,2] / (pixback[:,:,2] * bmax )
datanew = numpy.array((r,g,b))
imnew = scipy.misc.toimage(datanew)
The code is not tested, but should work somehow with minor modifications.
This is partially from FolksTalk webpage:
from PIL import Image
import numpy as np
# Read image file
in_file = "my_image.png"
# convert('RGB') for PNG file type
image = Image.open(in_file).convert('RGB')
pixels = np.asarray(image)
# Convert from integers to floats
pixels = pixels.astype('float32')
# Normalize to the range 0-1
pixels /= 255.0