Optimization Basic Image Processing Python - python

I'm trying to accomplish a basic image processing. Here is my algorithm :
Find n., n+1., n+2. pixel's RGB values in a row and create a new image from these values.
I'm taking first pixel's red value,second pixel's green value and third pixel's blue value and create pixel. This operation continue for every row in image.
Here is my example code in python :
import glob
import ntpath
import numpy
from PIL import Image
images = glob.glob('balls/*.png')
data_compressed = numpy.zeros((540, 2560, 3), dtype=numpy.uint8)
for image_file in images:
print(f'Processing [{image_file}]')
image = Image.open(image_file)
data = numpy.loadasarray(image)
for i in range(0, 2559):
for j in range(0, 539):
pix_x = j * 3 + 1
red = data[pix_x - 1, i][0]
green = data[pix_x, i][1]
blue = data[pix_x + 1, i][2]
data_compressed[j, i] = [red, green, blue]
im = Image.fromarray(data_compressed)
image_name = ntpath.basename(image_file)
im.save(f'export/{image_name}')
My input and output images are in RGB format. My code is taking 5 second for every image. I'm open for any idea to optimization this task. I can use c++ or any other languages if necessary.

data_compressed = np.concatenate((
np.expand_dims(data[0:-2][:,:,0], axis=2),
np.expand_dims(data[1:-1][:,:,1], axis=2),
np.expand_dims(data[2:][:,:,2], axis=2)), axis=2)
Image1 : Original image
Image2: Original image shifted by one pixel
Image3: Original image shifted by two pixel
Take channel 0 of Image1, channel 1 of Image2 and channel 3 of Image3 concatenate.
Sample
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
image = Image.open("Lenna.png")
data = numpy.asarray(image)
data_compressed = np.concatenate((
np.expand_dims(data[0:-2][:,:,0], axis=2),
np.expand_dims(data[1:-1][:,:,1], axis=2),
np.expand_dims(data[2:][:,:,2], axis=2)), axis=2)
new_image = Image.fromarray(data_compressed)
If you want a stride over 3 pixels for calculating the next pixel again then you can use numpy slicing
new_image = Image.fromarray(data_compressed[:, ::3])
Original Image:
Transformed Image with 3 stride:

Well if are only looking for a speed up you should take a look at the module Cython. It lets you specify the type of different variables and then compile the script to functioning c code. This can often lead to great improvements when it comes to time complexity.

With plain python there's only so much you can do. Here is a small optimization which can help a bit since it will allocate less memory. Otherwise I would look at Cython/Numba as said previously or using other languages.
data_compressed[j, i, 0] = data[pix_x - 1, i][0]
data_compressed[j, i, 1] = data[pix_x, i][1]
data_compressed[j, i, 2] = data[pix_x + 1, i][2]

Related

Add diff of images into one image (Linux/Python)

I'm looking for a way to blend only the differences of images into one image. I'm looking for a linux command or a way to achieve this with python.
Example:
Source images:
The result should be:
Another usecase:
http://3.bp.blogspot.com/-h3yuVc0hyvc/ToqQDE0Bf4I/AAAAAAAAGj0/HON-gM_9PhU/s1600/JayBumpOllieStichedFinishedRS.jpg
Thanks!!
Vince
It would make sense to start from the image that contains background only and compare each frame with it. The background can be computed as median over the whole sequence. If we assume that background median image was a0.jpg and following three frames with 3 dots would be a1.jpg, a2.jpg and a3.jpg, then merging them together can be done using compare_images function of the scikit-image and modifying the values only at those pixels where the change was encountered. Note that due to compression there is a tolerance threshold (th) set to 0.1. You can play with that value (0,1) for more or less sensitivity.
Following script should to something like that:
import skimage.io as io
from skimage.util import compare_images
import numpy as np
im0 = io.imread('a0.jpg') # median of source images
im1 = io.imread('a1.jpg') # source image 1
im2 = io.imread('a2.jpg') # source image 2
im3 = io.imread('a3.jpg') # source image 3
im_all = np.copy(im0)
th = 0.1
# d = np.max(np.abs(im2 - im0), -1)
d = compare_images(im1, im0, method='diff')
d= np.max(np.abs(d), -1)
im_all[d>th] = im1[d>th]
io.imsave("d1.jpg", d>th)
d = compare_images(im2, im0, method='diff')
d= np.max(np.abs(d), -1)
im_all[d>th] = im2[d>th]
io.imsave("d2.jpg", d>th)
d = compare_images(im3, im0, method='diff')
d= np.max(np.abs(d), -1)
im_all[d>th] = im3[d>th]
io.imsave("d3.jpg", d>th)
io.imsave("im_all.jpg", im_all)
This is not exactly what I asked, but it does the job good enough for my needs:
convert 1.jpg 2.jpg 3.jpg -evaluate-sequence max evalresult.png
With the example image with the clouds it doesn't work really good (because the clouds are white), but in another context it is great (when the differences are brighter than the background)

Convert a large grayscale PIL image to black and transparent

I am trying to use a large 2d array to create an image mask with black and transparent parts. Originally, the input 2d array was a PIL.Image that was loaded in grayscale ('L') mode. So it contains values between 0 and 255. And now I want to replace all the 0s with [0,0,0,255] (black stays black) and all values >0 should be [0,0,0,0] (transparent). I can do this simply like this:
import numpy as np
# generate some random test data - normally I just read the input image, which is fast
input_data = np.array([np.array([random.choice([0,10]) for x in range(22000)]) for y in range(9000)])
# create a new img containing black and transparent pixels (r,g,b,alpha) and this takes ages
overlay_img = [[[0, 0, 0, 255] if input_data[y][x] == 0 else [0, 0, 0, 0] for x in range(len(input_data[0]))] for y in range(len(input_data))]
overlay_img = np.array(overlay_img)
This takes quite some time because the input data is so large (~22000x9000). I am curious if it is somehow possible to do this faster. I also tried np.where, but I could not get it to work. Maybe there is even a way to directly change the PIL image?
fyi: In the end, I just want to plot this image on top of my matplotlib plot with imshow, so that only the relevant regions are visible (where the image is transparent) and the rest is hidden/black.
Here just a very quick and small example of what I want to do:
I think you want this, but you haven't shown your code for imshow():
#!/usr/bin/env python3
import random
import numpy as np
# Set up dimensions and random input image
h, w = 9000, 22000
im = np.random.randint(0, 11, (h,w), dtype=np.uint8)
# Create 4-channel mask image
mask = np.zeros((h,w,4), dtype=np.uint8)
mask[...,3] = (im==0) * 255
The last line takes 800ms on my MacBook Pro.
If you need a bit more performance, you can use numexpr as follows and the time required is 300ms instead of 800ms:
import random
import numexpr as ne
import numpy as np
# Set up dimensions and random input image
h, w = 9000, 22000
im = np.random.randint(0, 11, (h,w), dtype=np.uint8)
# Create 4-channel mask image
mask = np.zeros((h,w,4), dtype=np.uint8)
# Same but with "numexpr"
mask[...,3] = ne.evaluate("(im==0)*255")

When converting an image into an array and viceversa, are there extra considerations one must take into account?

I wrote this code to switch the red and blue values in the RGB array from a given image:
from PIL import Image
import numpy as np
image = Image.open("image.jpg")
RGBarr = np.asarray(image)
newRGB = np.full_like(RGBarr, 1)
red = RGBarr[..., 0]
green = RGBarr[...,1]
blue = RGBarr[..., 2]
newRGB[..., 0] = blue
newRGB[..., 1] = green
newRGB[..., 2] = red
inv_image = Image.fromarray(newRGB, 'RGB')
inv_image.save('inv_image.png')
inv_image.show()
I tried it with multiple images, and it works almost every time. However, in some cases I get the following error:
raise ValueError("not enough image data")
ValueError: not enough image data
That can be fixed if I do not specify the mode in Image.fromarray(obj, mode), but even doing that I am not sure if the result I obtain is the "correct" one.
Is there a way to determine what mode should be used for a certain image?
I hope this is not a dumb question, but I am sort of new in this image processing business.
The error occurs, when you try to read images which are not RGB like grayscale images or RGBA images. To keep the rest of your code valid, the easiest way would be to enforce RGB input by using:
image = Image.open("image.jpg").convert('RGB')
Then, possible grayscale or RGBA images are converted to RGB, and can be processed as regular RGB images.
As you found out yourself,
inv_image = Image.fromarray(newRGB)
also works, but the processing from the rest of your code then isn't correct anymore (no proper slicing of the desired dimensions/axes). That would require further work on your code to also respect grayscale or RGBA images.
Hope that helps!
EDIT: To incorporate furas' idea to get rid of NumPy, here's a PIL only way of swapping the channels. Notice: You still need the enforced RGB input.
from PIL import Image
image = Image.open('image.jpg').convert('RGB')
r, g, b = image.split()
inv_image = Image.merge('RGB', (b, g, r))
inv_image.save('inv_image.png')
inv_image.show()
If you want to re-order RGB channels to BGR with Numpy, it is much simpler to do this:
BGR = RGB[...,::-1]
which just addresses the last index (i.e. the channels) in reverse. It has the benefit of being O(1) which means it takes the same amount of time regardless of the size of the array. On my Mac, it takes 180ns to do BGR->RGB with 10x10 image and just the same with a 10,000x10,000 image.
In general, you may want some other ordering rather than straight reversal, so if you want BGR->BRG, you can do:
BRG = BGR[...,(0,2,1)]
Or, if you want to make a 3-channel greyscale image by repeating the Green channel three times (because the green is usually the least noisy - see Wikipedia Bayer array article), you can simply do this:
RGBgrey = BGR[...,(1,1,1)]
If you want to get rid of Numpy, you can do it straight in PIL/Pillow using a matrix multiplication:
# Open image
im = Image.open('image.jpg')
# Define matrix to re-order RGB->BGR
Matrix = ( 0, 0, 1, 0,
0, 1, 0, 0,
1, 0, 0, 0)
# BGR -> RGB
BGR = im.convert("RGB", Matrix)
You can understand the matrix like this:
newR = 0*oldR + 0*oldG + 1*oldB + 0 offset
newG = 0*oldR + 1*oldG + 0*oldB + 0 offset
newB = 1*oldR + 0*oldG + 0*oldB + 0 offset
Input
Result

Python -- change the RGB values of the image and save as a image

I can read every pixel' RGB of the image already, but I don't know how to change the values of RGB to a half and save as a image.Thank you in advance.
from PIL import *
def half_pixel(jpg):
im=Image.open(jpg)
img=im.load()
print(im.size)
[xs,ys]=im.size #width*height
# Examine every pixel in im
for x in range(0,xs):
for y in range(0,ys):
#get the RGB color of the pixel
[r,g,b]=img[x,y]
get the RGB color of the pixel
[r,g,b]=img.getpixel((x, y))
update new rgb value
r = r + rtint
g = g + gtint
b = b + btint
value = (r,g,b)
assign new rgb value back to pixel
img.putpixel((x, y), value)
You can do everything you are wanting to do within PIL.
If you are wanting to reduce the value of every pixel by half, you can do something like:
import PIL
im = PIL.Image.open('input_filename.jpg')
im.point(lambda x: x * .5)
im.save('output_filename.jpg')
You can see more info about point operations here: https://pillow.readthedocs.io/en/latest/handbook/tutorial.html#point-operations
Additionally, you can do arbitrary pixel manipulation as:
im[row, col] = (r, g, b)
There are many ways to do this with Pillow. You can use Image.point, for example.
# Function to map over each channel (r, g, b) on each pixel in the image
def change_to_a_half(val):
return val // 2
im = Image.open('./imagefile.jpg')
im.point(change_to_a_half)
The function is actually only called 256 times (assuming 8-bits color depth), and the resulting map is then applied to the pixels. This is much faster than running a nested loop in python.
If you have Numpy and Matplotlib installed, one solution would be to convert your image to a numpy array and then e.g. save the image with matplotlib.
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
img = Image.open(jpg)
arr = np.array(img)
arr = arr/2 # divide each pixel in each channel by two
plt.imsave('output.png', arr.astype(np.uint8))
Be aware that you need to have a version of PIL >= 1.1.6

convert image to value matrix

I have an image which is like a chess board with 4 colors (Black, white, Red, Blue). I have to convert this image to a matrix of numbers: 1 for white, 2 for black, 3 for red so on.
For example the image:
should be converted to the matrix:
[[1,2,1,2,1,2...]
[2,1,2,1,2,1...]
...]
I'd prefer a solution in python.
I am not sure about SVG Images but lets suppose you have an image format readable by PIL (e.g. GIF, TIFF, JPEG, BMP, ...). Then you can read it using PIL like that:
import Image
img = Image.open("Chess_Board.bmp")
Now we want do do quantization, so the image pixels are not RGB anymore but a color index from 0 to 3 (suppose you want 4 different colors):
quantized = img.convert('P', palette=Image.ADAPTIVE, colors=4)
Next I suppose we convert it to numpy for easier access of the individual pixels. Then we do numpy magic to count how many go into one block:
import numpy as np
a = np.array(quantized)
blockLengthX = np.argmin(a[0]==a[0,0])
blockLengthY = np.argmin(a[:,0]==a[0,0])
After that it is easy. We just access the array using stepsize blockLengthX for cols and blockLengthY for rows:
result = a[::blockLengthX, ::blockLengthY]
Of course this assumes all of your blocks are exactly the same size.
Here is the complete program for easier copy and paste. I also shortened a bit:
import Image
import numpy as np
img = Image.open("Chess_Board.bmp")
a = np.array(img.convert('P', palette=Image.ADAPTIVE, colors=4))
blockLengthX = np.argmin(a[0]==a[0,0])
blockLengthY = np.argmin(a[:,0]==a[0,0])
result = a[::blockLengthX, ::blockLengthY]

Categories