Converting multiple numpy images to gray scale - python

I currently have a numpy array 'images' containing 2000 photos. I am looking for an improved way of converting all the photos in 'images' to gray scale. The shape of the images is (2000, 100, 100, 3). This is what I have so far:
# Function takes index value and convert images to gray scale
def convert_gray(idx):
gray_img = np.uint8(np.mean(images[idx], axis=-1))
return gray_img
#create list
g = []
#loop though images
for i in range(0, 2000):
#call convert to gray function using index of image
gray_img = convert_gray(i)
#add grey image to list
g.append(gray_img)
#transform list of grey images back to array
gray_arr = np.array(g)
I wondered if anyone could suggest a more efficient way of doing this? I need the output in an array format

With your mean over the last axis you do right now:
Gray = 1/3 * Red + 1/3 * Green + 1/3 * Blue
But actually another conversion formula is more common (See this answer):
Gray = 299/1000 * Red + 587/1000 * Green + 114/1000 * Blue
The code provided by #unutbu also works for arrays of images:
import numpy as np
def rgb2gray(rgb):
return np.dot(rgb[...,:3], [0.2989, 0.5870, 0.1140])
rgb = np.random.random((100, 512, 512, 3))
gray = rgb2gray(rgb)
# shape: (100, 512, 512)

Related

How to iterate over multiple images of different dimensions and stack them into a single picture horizontally?

I have a multiple pictures with different dimensions. I have been trying to concat them horizontally using openCV.
The process is kind of following:
Iterate over all the images to find the max width and total height.
Create a black mask that is with the size of max width and total height got from all the images.
Stack all the images horizontally on that black mask.
I am not sure how to do this thing. Kindly help me!
Images are just 3D matrices, so you can do this very easily by creating a matrix of zeros (= black) of the desired size, then filling in your images.
I've created fake images here but you can use cv2 to read in your real images.
import numpy as np
import matplotlib.pyplot as plt
# create three images of different shapes and different shades of grey
img1 = np.ones((100, 200, 3), dtype=int)*50
img2 = np.ones((200, 400, 3), dtype=int)*100
img3 = np.ones((100, 300, 3), dtype=int)*150
imgs = [img1, img2, img3]
# get max width and total height
max_width = 0
total_height = 0
for img in imgs:
total_height += img.shape[0]
max_width = max(max_width, img.shape[1])
# make black canvas of appropriate shape
canvas = np.zeros((total_height, max_width, 3), dtype=int)
# stack images on canvas
start_height = 0
for img in imgs:
print(img.shape)
canvas[start_height:start_height+img.shape[0], 0:img.shape[1], :] = img
start_height+= img.shape[0]
# show results
plt.imshow(canvas)
This produces the following result:

Colour of image is being changed after conversion from PIL image to numpy array and converting it back

Here is my code
img = Image.open('./data/imgs/' + '0.jpg')
//converting to numpy array
img.load()
imgdata = np.asarray(img)
np.interp(imgdata, (imgdata.min(), imgdata.max()), (0, +1))
//converting back to PIL image
image = imgdata * 255
image = Image.fromarray(image.astype('uint8'), 'RGB')
image.show()
Here is my output with color distortion
How to solve this?
Reason for the problem:
You are not using the return value of np.interp, so the the imgdata is not replaced by the new values. Thus it retains the initial range of values as [0, 255] and its dtype also is np.uint8. When you did imgdata * 255 it could not fit the results in np.uint8 and it overflowed(but starting from 0 again as it's unsigned).
Assumption:
I assume that you wanted to map the image values from [min(), max()] to [0, 1] and then rescale it back by multiplying it with 255.
Solution:
If my assumption about your code is true, do this:
imgdata = np.interp(imgdata, (imgdata.min(), imgdata.max()), (0, +1))
else remove (* 255) from the code and change it to,
image = imgdata
You can test this overflow by taking a sample array as follows:
sample = np.array([10, 20, 30], dtype=np.uint8)
sample2 = sample * 10
print(sample, sample2)
you will see that values of sample2 are [100, 200, 44] but not [100, 200, 300]

How to merge multiple pictures diagonally into a single one using Python

I'm trying to merge multiple images diagonally into a single one using Python.
I checked a lot of questions but didn't find something similar to my need.
All I can do right now is a simple merge of files on top of each other:
from PIL import Image
import numpy as np
img = Image.open("1.png")
background = Image.open("2.png")
background.paste(img, (0, 0), img)
background.save('result.png',"PNG")
Here are the pictures to test :
image1, image2, image3
I need the pictures to be arranged diagonally to fit into a final 900 x 1200 px size picture with white Background. Probably they need to be sized down a bit and fit ? At least that's the process I am doing in Photoshop, manually (time consuming).
Sometimes there's 2 pictures to fit, sometimes could be 4 or 5.
This should do the job:
from PIL import Image
images = ['1.png', '2.png', '3.png']
# shift between images
offset = (200, 100)
target_size = (900, 1200)
images = [Image.open(fn) for fn in images]
no_img = len(images)
image_size = [s+no_img*o for s, o in zip(images[0].size, offset)]
#create empty background
combined_image = Image.new('RGBA', image_size)
# paste each image at a slightly shifted position, start at top right
for idx, image in enumerate(images):
combined_image.paste(image, ((no_img - idx - 1) * offset[0], idx * offset[1]), image)
# crop to non-empty area
combined_image = combined_image.crop(combined_image.getbbox())
# resizing and padding such that it fits 900 x 1200 px
scale = min(target_size[0] / combined_image.size[0], target_size[1] / combined_image.size[1])
combined_image = combined_image.resize((int(combined_image.size[0] * scale), int(combined_image.size[1] * scale)), Image.BICUBIC)
img_w, img_h = combined_image.size
finale_output = Image.new('RGB', target_size, (255, 255, 255))
offset = ((target_size[0] - img_w) // 2, (target_size[1] - img_h) // 2)
finale_output.paste(combined_image, offset, combined_image)
# display
finale_output.show()
EDIT: I added the code for resizing and padding such that the output is exactly of your wanted size (whilst maintaining the aspect ratio).

Conversion from numpy array to QImage/QPixmap results in colored stripes

I have an RGB image 224x224x3 and an overlay 224x224.
And I want to apply my overlay as red pixels on my RGB image, which I convert to grayscale. The overlay range from 0 to 255. Higher values should make a stronger red.
I tried to use Stefan's tutorial, but I could not adapt it.
The result is just a mainly black picture, which changes a little bit with nAlpha:
Here is my code:
# RGB input, shape (224x224x3)
img = self.inputImage
# convert to grayscale
img = np.average(img, axis=2)
rows, cols = img.shape[0], img.shape[1]
color_mask = np.zeros((rows, cols, 3))
# convert to uint8 to plot in QImage::Format_RGB888
img = img.astype(np.uint8)
overlay = self.outputImage.astype(np.uint8)
# normalize to range 0 to 1
img = (img*1.0-img.min())/(img.max()-img.min())
overlay = (overlay*1.0 - overlay.min()) / (overlay.max() - overlay.min())
# create a mask, where only the red channels contains values
mask = np.zeros((rows,cols,3))
mask[:,:,0] = mask[:,:,0]+overlay
color_mask = mask
img_color = np.dstack((img, img, img))
# make everysthing to HSV colorspace
img_hsv = color.rgb2hsv(img_color)
color_mask_hsv = color.rgb2hsv(color_mask)
img_hsv[..., 0] = color_mask_hsv[..., 0]
img_hsv[..., 1] = color_mask_hsv[..., 1] * nAlpha
# convert back
img_masked = color.hsv2rgb(img_hsv)
# rescale
ov = img_masked*255
self.mainWindow_images.label_outputImg.setPixmap(
QPixmap(QImage(ov, ov.shape[1], ov.shape[0], ov.shape[1] * 3, QImage.Format_RGB888)))
I've come to this problem recently.
You should convert the image into an ndarray of data type uint8, like this.
ov = (img_masked*255).astype('uint8')
qimg = QImage(ov, ov.shape[1], ov.shape[0], ov.shape[1] * 3, QImage.Format_RGB888)

Categorical image in python

I would like to transform a .jpg into a categorical array. For each pixel of the images I have RGB values and I would like to associate this values to a unique value (see images). Have you any idea to do this? I've made some research in scikit image and other image processing modules but without success.
The first part solution is found in https://stackoverflow.com/a/30524039/3104727). It is reproduced here in order to work it with this image
from PIL import Image
import operator
from collections import defaultdict
import numpy as np
input_path = 'TI_test.jpg'
output_path = 'TI_output.png'
size = (200,200)
# Then we declare the palette - this should contain all colours.
palette = [(112, 137, 98), #green
(96, 97, 115), #blue
(140, 129, 49), #gold
(184, 31, 36), #red
]
while len(palette) < 256:
palette.append((0, 0, 0))
# The code below will declare palette for PIL, since PIL needs flat
# array rather than array of tuples:
flat_palette = reduce(lambda a, b: a+b, palette)
assert len(flat_palette) == 768
# Now we can declare an image that will hold the palette. We'll use
# it to reduce the colours from the original image later.
palette_img = Image.new('P', (1, 1), 0)
palette_img.putpalette(flat_palette)
# Here we open the image and quantize it. We scale it to size eight
# times bigger than needed, since we're going to sample the average
# output later.
multiplier = 8
img = Image.open(input_path)
img = img.resize((size[0] * multiplier, size[1] * multiplier),Image.BICUBIC)
img = img.quantize(palette=palette_img) #reduce the palette
# We need to convert it back to RGB so that we can sample pixels now:
img = img.convert('RGB')
# Now we're going to construct our final image. To do this, we'll
# sample how many pixels of each palette color each square in the
# bigger image contains. Then we'll choose the color that occurs most
# often.
out = Image.new('RGB', size)
for x in range(size[0]):
for y in range(size[1]):
#sample at get average color in the corresponding square
histogram = defaultdict(int)
for x2 in range(x * multiplier, (x + 1) * multiplier):
for y2 in range(y * multiplier, (y + 1) * multiplier):
histogram[img.getpixel((x2,y2))] += 1
color = max(histogram.iteritems(),key=operator.itemgetter(1))[0]
out.putpixel((x, y), color)
The following code is added to transform RGB image in grayscale and then in an array of categorical value (0 to n colours).
out2 = out.convert('L')
List of unique grayscale values
color = list(set(list(out2.getdata())))
Associate categorical value (0 to n colours) to each pixel
for x in range(size[0]):
for y in range(size[1]):
if out2.getpixel((x,y)) == color[0]:
out2.putpixel((x,y),0)
elif out2.getpixel((x,y)) == color[1]:
out2.putpixel((x,y),1)
elif out2.getpixel((x,y)) == color[2]:
out2.putpixel((x,y),2)
else:
out2.putpixel((x,y),3)
Transform the image to a numpy array
pix = np.array(out2)

Categories