Python 3.5: PIL Image.fromarray producing nonsense image - python

I have an RGB image. When I import this image, I convert it to HSV using matplotlib.color and save the resulting array in a dict. When I want to display this image, I use Image.fromarray with mode = 'HSV'. I'm not sure what I am doing wrong but when the image is displayed, I get a mess (seen below along with code). Any help is appreciated. The code snippets below are roughly what happens in order to any given set of imported images.
RGB to HSV Code:
from skimage import io
import matplotlib.colors as mpclr
import glob
import os
from PIL import Image, ImageOps
types = ("\*.tif", "\*.jpg", "\*.ppm")
imagePath = []
def importAllImgs(folderPath):
for ext in types:
imagePath.extend(glob.glob(folderPath + ext))
im_coll = io.ImageCollection(imagePath, conserve_memory = True)
im_array = []
for i in range(len(im_coll)):
#CONVERSION HAPPENS HERE
image = im_coll[i]
fltImg = np.around((np.array(image)/255.0), decimals = 2)
imgHSV = mpclr.rgb_to_hsv(fltImg)
im_array.append(imgHSV)
return im_array, imagePath
Storage of Data:
def organizeAllData(self, imgArrList, imgPathList):
self.allImages = dict()
self.imageKeys = imgPathList
for i in range(len(imgPathList)):
self.allImages[imgPathList[i]] = {'H': imgArrList[i][:, :, 0],
'S': imgArrList[i][:, :, 1],
'V': imgArrList[i][:, :, 2]}
self.hsvValues = []
self.labelValues = []
return self.allImages
Construction of array for displaying image:
def getImage(self, imageOfInterest):
H = self.allImages[imageOfInterest]['H'][:,:]
S = self.allImages[imageOfInterest]['S'][:,:]
V = self.allImages[imageOfInterest]['V'][:,:]
imgArray = np.dstack((H,S,V))
return imgArray
Displaying of Image:
preImArray = halThrThsnd.getImage(self.imagePaths[self.imageIndex])
self.preIm = Image.fromarray(preImArray, 'HSV')
And finally, the resulting image:

As per user sascha's comment (see below question), I decided to normalize the libraries I'm using for HSV conversion. Once I did that, I got normal images no problem. It turns out that depending on what library you use for image conversion, you will get different HSV value ranges. Some libraries will produce a range from 0 to 1. Others will produce a range from 0 to 255.
Tl;dr: Used the same library across all processes, got a good image.

Related

Python: Convert image from RGB to YDbDr color space

Trying to convert image from RGB color space to YDbDr color space according to the formula:
Y = 0.299R + 0.587G + 0.114B
Db = -0.45R - 0.883G +1.333B
Dr = -1.333R + 1.116G + 0.217B
With the following code I'm trying to show only Y channel which should be grayscale image but I keep getting image all in blue color:
import numpy as np
from PIL import Image
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("./pics/Slike_modela/Test/Proba/1_Color.png")
new_img = []
for row in img:
new_row = []
for pixel in row:
Y = 0.299*pixel[2]+0.587*pixel[1]+0.114*pixel[0]
Db = -0.45*pixel[2]-0.883*pixel[1]+1.333*pixel[0]
Dr = -1.333*pixel[2]+1.116*pixel[1]+0.217*pixel[0]
new_pixel = [Y, Db, Dr]
new_row.append(new_pixel)
new_img.append(new_row)
new_img_arr = np.array(new_img)
new_img_arr_y = new_img_arr.copy()
new_img_arr_y[:,:,1] = 0
new_img_arr_y[:,:,2] = 0
print (new_img_arr_y)
cv2.imshow("y image", new_img_arr_y)
key = cv2.waitKey(0)
When printing the result array I see correct numbers according to formula and correct shape of the array.
What is my mistake? How to get Y channel image i.e. grayscale image?
When processing images with Python, you really, really should try to avoid:
treating images as lists and appending millions and millions of pixels, each of which creates a whole new object and takes space to administer
processing images with for loops, which are very slow
The better way to deal with both of these is through using Numpy or other vectorised code libraries or techniques. That is why OpenCV, wand, scikit-image open and handle images as Numpy arrays.
So, you basically want to do a dot product of the colour channels with a set of 3 weights:
import cv2
import numpy as np
# Load image
im = cv2.imread('paddington.png', cv2.IMREAD_COLOR)
# Calculate Y using Numpy "dot()"
Y = np.dot(im[...,:3], [0.114, 0.587, 0.299]).astype(np.uint8)
That's it.

convert IR image to RGB with python

The code below is intended to take an infrared image (B&W) and convert it to RGB. It does so successfully, but with significant noise. I have included a few lines for noise reduction but they don't seem to help. I've included the starting/resulting photos below. Any advice/corrections are welcome and thank you in advance!
from skimage import io
import numpy as np
import glob, os
from tkinter import Tk
from tkinter.filedialog import askdirectory
import cv2
path = askdirectory(title='Select PNG Folder') # shows dialog box and return the path
outpath = askdirectory(title='Select SAVE Folder')
# wavelength in microns
MWIR = 4.5
R = .642
G = .532
B = .44
vector = [R, G, B]
vectorsum = np.sum(vector)
vector = vector / vectorsum #normalize
vector = vector*255 / MWIR #changing this value changes the outcome significantly so I
#have been messing with it in the hopes of fixing it but no luck so far.
vector = np.power(vector, 4)
for file in os.listdir(path):
if file.endswith(".png"):
imIn = io.imread(os.path.join(path, file))
imOut = imIn * vector
ret,thresh = cv2.threshold(imOut,64,255,cv2.THRESH_BINARY)
kernel = np.ones((5, 5), np.uint8)
erode = cv2.erode(thresh, kernel, iterations = 1)
result = cv2.bitwise_or(imOut, erode)
io.imsave(os.path.join(outpath, file) + '_RGB.png',imOut.astype(np.uint8))
Your noise looks like completely random values, so I suspect you have an error in your conversion from float to uint8. But instead of rolling everything for yourself, why don't you just use:
imOut = cv2.cvtColor(imIn,cv2.COLOR_GRAY2BGR)
Here is one way to do that in Python/OpenCV.
Your issue is likely that your channel values are exceeding the 8-bit range.
Sorry, I do not understand the relationship between your R,G,B weights and your MWIR. Dividing by MWIR will do nothing if your weights are properly normalized.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('car.jpg')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# make color channels
red = gray.copy()
green = gray.copy()
blue = gray.copy()
# set weights
R = .642
G = .532
B = .44
MWIR = 4.5
# get sum of weights and normalize them by the sum
R = R**4
G = G**4
B = B**4
sum = R + G + B
R = R/sum
G = G/sum
B = B/sum
print(R,G,B)
# combine channels with weights
red = (R*red)
green = (G*green)
blue = (B*blue)
result = cv2.merge([red,green,blue])
# scale by ratio of 255/max to increase to fully dynamic range
max=np.amax(result)
result = ((255/max)*result).clip(0,255).astype(np.uint8)
# write result to disk
cv2.imwrite("car_colored.png", result)
# display it
cv2.imshow("RESULT", result)
cv2.waitKey(0)
Result
If the noise is coming from the sensor itself, like a grainy noise, you'll need to look into denoising algorithms. scikit-image and opencv provide some denoising algorithms you can try. Maybe take a look at this and this.
I recently learned about matplotlib.cm, which handles colormaps. I've been using those to artificially color IR images, and made a brief example using the same black & white car image used above. Basically, I create a colormap .csv file locally, then refer to it for RGB weights. You may have to pick and choose which colormap you prefer, but that's up to personal preference.
Input image:
Python:
import os
import numpy as np
import cv2
from matplotlib import cm
# Multiple colormap options are available- I've hardcoded viridis for this example.
colormaps = ["viridis", "plasma", "inferno", "magma", "cividis"]
def CreateColormap():
if not os.path.exists("viridis_colormap.csv"):
# Get 256 entries from "viridis" or any other Matplotlib colormap
colormap = cm.get_cmap("viridis", 256)
# Make a Numpy array of the 256 RGB values
# Each line corresponds to an RGB colour for a greyscale level
np.savetxt("viridis_colormap.csv", (colormap.colors[...,0:3]*255).astype(np.uint8), fmt='%d', delimiter=',')
def RecolorInfraredImageToRGB(ir_image):
# Load RGB lookup table from CSV file
lookup_table = np.loadtxt("viridis_colormap.csv", dtype=np.uint8, delimiter=",")
# Make output image, same height and width as IR image, but 3-channel RGB
result = np.zeros((*ir_image.shape, 3), dtype=np.uint8)
# Take entries from RGB LUT according to greyscale values in image
np.take(lookup_table, ir_image, axis=0, out=result)
return result
if __name__ == "__main__":
CreateColormap()
img = cv2.imread("bwcar.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
recolored = RecolorInfraredImageToRGB(gray)
cv2.imwrite("car_recolored.png", recolored)
cv2.imshow("Viridis recolor", recolored)
cv2.waitKey(0)
Output:

Use PIL to recolor a monochrome image and preserve transparency [duplicate]

Okay, here's the situation:
I want to use the Python Image Library to "theme" an image like this:
Theme color: "#33B5E5"
IN:
OUT:
I got the result using this commands with ImageMagick:
convert image.png -colorspace gray image.png
mogrify -fill "#33b5e5" -tint 100 image.png
Explanation:
The image is first converted to black-and-white, and then it is themed.
I want to get the same result with the Python Image Library.
But it seems I'm having some problems using it since:
Can not handle transparency
Background (transparency in main image) gets themed too..
I'm trying to use this script:
import Image
import ImageEnhance
def image_overlay(src, color="#FFFFFF", alpha=0.5):
overlay = Image.new(src.mode, src.size, color)
bw_src = ImageEnhance.Color(src).enhance(0.0)
return Image.blend(bw_src, overlay, alpha)
img = Image.open("image.png")
image_overlay(img, "#33b5e5", 0.5)
You can see I did not convert it to a grayscale first, because that didn't work with transparency either.
I'm sorry to post so many issues in one question, but I couldn't do anything else :$
Hope you all understand.
Note: There's a Python 3/pillow fork of PIL version of this answer here.
Update 4: Guess the previous update to my answer wasn't the last one after all. Although converting it to use PIL exclusively was a major improvement, there were a couple of things that seemed like there ought to be better, less awkward, ways to do, if only PIL had the ability.
Well, after reading the documentation closely as well as some of the source code, I realized what I wanted to do was in fact possible. The trade-off was that now it has to build the look-up table used manually, so the overall code is slightly longer. However the result is that it only needs to make one call to the relatively slow Image.point() method, instead of three of them.
from PIL import Image
from PIL.ImageColor import getcolor, getrgb
from PIL.ImageOps import grayscale
def image_tint(src, tint='#ffffff'):
if Image.isStringType(src): # file path?
src = Image.open(src)
if src.mode not in ['RGB', 'RGBA']:
raise TypeError('Unsupported source image mode: {}'.format(src.mode))
src.load()
tr, tg, tb = getrgb(tint)
tl = getcolor(tint, "L") # tint color's overall luminosity
if not tl: tl = 1 # avoid division by zero
tl = float(tl) # compute luminosity preserving tint factors
sr, sg, sb = map(lambda tv: tv/tl, (tr, tg, tb)) # per component adjustments
# create look-up tables to map luminosity to adjusted tint
# (using floating-point math only to compute table)
luts = (map(lambda lr: int(lr*sr + 0.5), range(256)) +
map(lambda lg: int(lg*sg + 0.5), range(256)) +
map(lambda lb: int(lb*sb + 0.5), range(256)))
l = grayscale(src) # 8-bit luminosity version of whole image
if Image.getmodebands(src.mode) < 4:
merge_args = (src.mode, (l, l, l)) # for RGB verion of grayscale
else: # include copy of src image's alpha layer
a = Image.new("L", src.size)
a.putdata(src.getdata(3))
merge_args = (src.mode, (l, l, l, a)) # for RGBA verion of grayscale
luts += range(256) # for 1:1 mapping of copied alpha values
return Image.merge(*merge_args).point(luts)
if __name__ == '__main__':
import os
input_image_path = 'image1.png'
print 'tinting "{}"'.format(input_image_path)
root, ext = os.path.splitext(input_image_path)
result_image_path = root+'_result'+ext
print 'creating "{}"'.format(result_image_path)
result = image_tint(input_image_path, '#33b5e5')
if os.path.exists(result_image_path): # delete any previous result file
os.remove(result_image_path)
result.save(result_image_path) # file name's extension determines format
print 'done'
Here's a screenshot showing input images on the left with corresponding outputs on the right. The upper row is for one with an alpha layer and the lower is a similar one that doesn't have one.
You need to convert to grayscale first. What I did:
get original alpha layer using Image.split()
convert to grayscale
colorize using ImageOps.colorize
put back original alpha layer
Resulting code:
import Image
import ImageOps
def tint_image(src, color="#FFFFFF"):
src.load()
r, g, b, alpha = src.split()
gray = ImageOps.grayscale(src)
result = ImageOps.colorize(gray, (0, 0, 0, 0), color)
result.putalpha(alpha)
return result
img = Image.open("image.png")
tinted = tint_image(img, "#33b5e5")

JPG image into matrix using Python [duplicate]

I have an RGB image. I want to convert it to numpy array. I did the following
im = cv.LoadImage("abc.tiff")
a = numpy.asarray(im)
It creates an array with no shape. I assume it is a iplimage object.
You can use newer OpenCV python interface (if I'm not mistaken it is available since OpenCV 2.2). It natively uses numpy arrays:
import cv2
im = cv2.imread("abc.tiff",mode='RGB')
print(type(im))
result:
<type 'numpy.ndarray'>
PIL (Python Imaging Library) and Numpy work well together.
I use the following functions.
from PIL import Image
import numpy as np
def load_image( infilename ) :
img = Image.open( infilename )
img.load()
data = np.asarray( img, dtype="int32" )
return data
def save_image( npdata, outfilename ) :
img = Image.fromarray( np.asarray( np.clip(npdata,0,255), dtype="uint8"), "L" )
img.save( outfilename )
The 'Image.fromarray' is a little ugly because I clip incoming data to [0,255], convert to bytes, then create a grayscale image. I mostly work in gray.
An RGB image would be something like:
out_img = Image.fromarray( ycc_uint8, "RGB" )
out_img.save( "ycc.tif" )
You can also use matplotlib for this.
from matplotlib.image import imread
img = imread('abc.tiff')
print(type(img))
output:
<class 'numpy.ndarray'>
As of today, your best bet is to use:
img = cv2.imread(image_path) # reads an image in the BGR format
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR -> RGB
You'll see img will be a numpy array of type:
<class 'numpy.ndarray'>
Late answer, but I've come to prefer the imageio module to the other alternatives
import imageio
im = imageio.imread('abc.tiff')
Similar to cv2.imread(), it produces a numpy array by default, but in RGB form.
You need to use cv.LoadImageM instead of cv.LoadImage:
In [1]: import cv
In [2]: import numpy as np
In [3]: x = cv.LoadImageM('im.tif')
In [4]: im = np.asarray(x)
In [5]: im.shape
Out[5]: (487, 650, 3)
You can get numpy array of rgb image easily by using numpy and Image from PIL
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
im = Image.open('*image_name*') #These two lines
im_arr = np.array(im) #are all you need
plt.imshow(im_arr) #Just to verify that image array has been constructed properly
When using the answer from David Poole I get a SystemError with gray scale PNGs and maybe other files. My solution is:
import numpy as np
from PIL import Image
img = Image.open( filename )
try:
data = np.asarray( img, dtype='uint8' )
except SystemError:
data = np.asarray( img.getdata(), dtype='uint8' )
Actually img.getdata() would work for all files, but it's slower, so I use it only when the other method fails.
load the image by using following syntax:-
from keras.preprocessing import image
X_test=image.load_img('four.png',target_size=(28,28),color_mode="grayscale"); #loading image and then convert it into grayscale and with it's target size
X_test=image.img_to_array(X_test); #convert image into array
OpenCV image format supports the numpy array interface. A helper function can be made to support either grayscale or color images. This means the BGR -> RGB conversion can be conveniently done with a numpy slice, not a full copy of image data.
Note: this is a stride trick, so modifying the output array will also change the OpenCV image data. If you want a copy, use .copy() method on the array!
import numpy as np
def img_as_array(im):
"""OpenCV's native format to a numpy array view"""
w, h, n = im.width, im.height, im.channels
modes = {1: "L", 3: "RGB", 4: "RGBA"}
if n not in modes:
raise Exception('unsupported number of channels: {0}'.format(n))
out = np.asarray(im)
if n != 1:
out = out[:, :, ::-1] # BGR -> RGB conversion
return out
I also adopted imageio, but I found the following machinery useful for pre- and post-processing:
import imageio
import numpy as np
def imload(*a, **k):
i = imageio.imread(*a, **k)
i = i.transpose((1, 0, 2)) # x and y are mixed up for some reason...
i = np.flip(i, 1) # make coordinate system right-handed!!!!!!
return i/255
def imsave(i, url, *a, **k):
# Original order of arguments was counterintuitive. It should
# read verbally "Save the image to the URL" — not "Save to the
# URL the image."
i = np.flip(i, 1)
i = i.transpose((1, 0, 2))
i *= 255
i = i.round()
i = np.maximum(i, 0)
i = np.minimum(i, 255)
i = np.asarray(i, dtype=np.uint8)
imageio.imwrite(url, i, *a, **k)
The rationale is that I am using numpy for image processing, not just image displaying. For this purpose, uint8s are awkward, so I convert to floating point values ranging from 0 to 1.
When saving images, I noticed I had to cut the out-of-range values myself, or else I ended up with a really gray output. (The gray output was the result of imageio compressing the full range, which was outside of [0, 256), to values that were inside the range.)
There were a couple other oddities, too, which I mentioned in the comments.
We can use following function of open CV2 to convert BGR 2 RGB format.
RBG_Image = cv2.cvtColor(Image, cv.COLOR_BGR2RGB)
Using Keras:
from keras.preprocessing import image
img = image.load_img('path_to_image', target_size=(300, 300))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
Try timing the options to load an image to numpy array, they are quite similar. Go for plt.imread for simplicity and speed.
def time_this(function, times=100):
cum_time = 0
for t in range(times):
st = time.time()
function()
cum_time += time.time() - st
return cum_time / times
import matplotlib.pyplot as plt
def load_img_matplotlib(img_path):
return plt.imread(img_path)
import cv2
def load_img_cv2(img_path):
return cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
from PIL import Image
import numpy as np
def load_img_pil(img_path):
img = Image.open(img_path)
img.load()
return np.asarray( img, dtype="int32" )
if __name__=='__main__':
img_path = 'your_image_path'
for load_fn in [load_img_pil, load_img_cv2, load_img_matplotlib]:
print('-'*20)
print(time_this(lambda: load_fn(img_path)), 10000)
Result:
--------------------
0.0065201687812805175 10000 PIL, as in [the second answer][1]https://stackoverflow.com/a/7769424/16083419)
--------------------
0.0053211402893066405 10000 CV2
--------------------
0.005320906639099121 10000 matplotlib
You can try the following method. Here is a link to the docs.
tf.keras.preprocessing.image.img_to_array(img, data_format=None, dtype=None)
from PIL import Image
img_data = np.random.random(size=(100, 100, 3))
img = tf.keras.preprocessing.image.array_to_img(img_data)
array = tf.keras.preprocessing.image.img_to_array(img)

Colorize image while preserving transparency with PIL?

Okay, here's the situation:
I want to use the Python Image Library to "theme" an image like this:
Theme color: "#33B5E5"
IN:
OUT:
I got the result using this commands with ImageMagick:
convert image.png -colorspace gray image.png
mogrify -fill "#33b5e5" -tint 100 image.png
Explanation:
The image is first converted to black-and-white, and then it is themed.
I want to get the same result with the Python Image Library.
But it seems I'm having some problems using it since:
Can not handle transparency
Background (transparency in main image) gets themed too..
I'm trying to use this script:
import Image
import ImageEnhance
def image_overlay(src, color="#FFFFFF", alpha=0.5):
overlay = Image.new(src.mode, src.size, color)
bw_src = ImageEnhance.Color(src).enhance(0.0)
return Image.blend(bw_src, overlay, alpha)
img = Image.open("image.png")
image_overlay(img, "#33b5e5", 0.5)
You can see I did not convert it to a grayscale first, because that didn't work with transparency either.
I'm sorry to post so many issues in one question, but I couldn't do anything else :$
Hope you all understand.
Note: There's a Python 3/pillow fork of PIL version of this answer here.
Update 4: Guess the previous update to my answer wasn't the last one after all. Although converting it to use PIL exclusively was a major improvement, there were a couple of things that seemed like there ought to be better, less awkward, ways to do, if only PIL had the ability.
Well, after reading the documentation closely as well as some of the source code, I realized what I wanted to do was in fact possible. The trade-off was that now it has to build the look-up table used manually, so the overall code is slightly longer. However the result is that it only needs to make one call to the relatively slow Image.point() method, instead of three of them.
from PIL import Image
from PIL.ImageColor import getcolor, getrgb
from PIL.ImageOps import grayscale
def image_tint(src, tint='#ffffff'):
if Image.isStringType(src): # file path?
src = Image.open(src)
if src.mode not in ['RGB', 'RGBA']:
raise TypeError('Unsupported source image mode: {}'.format(src.mode))
src.load()
tr, tg, tb = getrgb(tint)
tl = getcolor(tint, "L") # tint color's overall luminosity
if not tl: tl = 1 # avoid division by zero
tl = float(tl) # compute luminosity preserving tint factors
sr, sg, sb = map(lambda tv: tv/tl, (tr, tg, tb)) # per component adjustments
# create look-up tables to map luminosity to adjusted tint
# (using floating-point math only to compute table)
luts = (map(lambda lr: int(lr*sr + 0.5), range(256)) +
map(lambda lg: int(lg*sg + 0.5), range(256)) +
map(lambda lb: int(lb*sb + 0.5), range(256)))
l = grayscale(src) # 8-bit luminosity version of whole image
if Image.getmodebands(src.mode) < 4:
merge_args = (src.mode, (l, l, l)) # for RGB verion of grayscale
else: # include copy of src image's alpha layer
a = Image.new("L", src.size)
a.putdata(src.getdata(3))
merge_args = (src.mode, (l, l, l, a)) # for RGBA verion of grayscale
luts += range(256) # for 1:1 mapping of copied alpha values
return Image.merge(*merge_args).point(luts)
if __name__ == '__main__':
import os
input_image_path = 'image1.png'
print 'tinting "{}"'.format(input_image_path)
root, ext = os.path.splitext(input_image_path)
result_image_path = root+'_result'+ext
print 'creating "{}"'.format(result_image_path)
result = image_tint(input_image_path, '#33b5e5')
if os.path.exists(result_image_path): # delete any previous result file
os.remove(result_image_path)
result.save(result_image_path) # file name's extension determines format
print 'done'
Here's a screenshot showing input images on the left with corresponding outputs on the right. The upper row is for one with an alpha layer and the lower is a similar one that doesn't have one.
You need to convert to grayscale first. What I did:
get original alpha layer using Image.split()
convert to grayscale
colorize using ImageOps.colorize
put back original alpha layer
Resulting code:
import Image
import ImageOps
def tint_image(src, color="#FFFFFF"):
src.load()
r, g, b, alpha = src.split()
gray = ImageOps.grayscale(src)
result = ImageOps.colorize(gray, (0, 0, 0, 0), color)
result.putalpha(alpha)
return result
img = Image.open("image.png")
tinted = tint_image(img, "#33b5e5")

Categories

Resources