Opening an image file to display in a frame or button in tkinter is simple, but I have images stored in arrays as raw RGB data (for transmission over SPI to a small LCD display). I wish to show this data as a proper image (exactly as processed for the small lcd display) in a frame or button, with tkinter.
I need some steering in how to approach this. Cheers.
You need to convert your array rgb data into an Image object that tkinter knows.
For this the best approach is to convert your rgb data to a PIL Image.
Depending on how your rgb values are ordered in the array you might need to do some conversions: if rgb data are interleaved (usually the case) or in separate channels/plans, the conversion might fail.
Check for here for converting an array to a pil image.
https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.fromarray
Once the conversion to Pil Image is done you can use it in tkinter.
from PIL import Image, ImageTk
import numpy as np
#convert array to PiL Image
PIL_image = Image.fromarray(np.uint8(your_rgb_array)).convert('RGB')
tk_image = ImageTk.PhotoImage(PIL_image )
label1 = tkinter.Label(image=tk_image )
For displaying the image you should have a look at tk.Canvas:
https://www.tutorialspoint.com/python/tk_canvas.htm
Thank you all for your input. I 've been chewing on it for a bit, and now have working code for the task. It takes the image as has been manipulated by numpy or opencv, transforms it to tk format and then shows the original and reduced bitdepth images side by side, with pixel doubling. Here is the snippet with the relevant code:
def nextimg(self):
f = self.image[self.ptr % self.length]
self.drawImage(f)
f = f[...,::-1]
f = np.repeat(f,2,axis=0)
f = np.repeat(f,2,axis=1)
temp=self.size
self.size=[480,480]
if self.bpp == 12: g = self.imageConvert_tk12(f)
elif self.bpp == 16: g = self.imageConvert_tk16(f)
else: g = self.imageConvert_tk18(f)
self.button_image1 = ImageTk.PhotoImage(Image.fromarray(np.uint8(f)).convert('RGB'))
self.button_image2 = ImageTk.PhotoImage(Image.fromarray(np.uint8(g)).convert('RGB'))
self.button_pic1.configure(image=self.button_image1)
self.button_pic2.configure(image=self.button_image2)
self.ptr+=1
self.size=temp
def imageConvert_tk18(self,image):
x,y = image.shape[0:2]
arr =np.ndarray((x,y,3))
arr[...,:] = (np.uint8(image[...,:] >> 2) * 4)
return arr
def imageConvert_tk16(self,image):
x,y = image.shape[0:2]
arr = np.ndarray((x,y,3))
arr[...,0:3:2] = (np.uint8(image[...,0:3:2] >> 3) * 8)
arr[...,1] = (np.uint8(image[...,1] >> 2) * 4)
return arr
def imageConvert_tk12(self,image):
x,y = image.shape[0:2]
arr =np.ndarray((x,y,3))
arr[...,:] = (np.uint8(image[...,:] >> 4) * 16)
return arr
I also understand a lot more now about lists and arrays. Opencv creates an np.ndarray , which I now understand is a 1-dimensional array (or list in list?) but with a given x-dimensional shape.
Of course I am hoping there might be another solution without having to resort to PIL as intermediary, but if not this will work fine.
Please feel free to suggest improvements to my code!
Related
I am trying to create a random image using NUMPY. First I am creating a random 3D array as it should be in the case of an image e.g. (177,284,3).
random_im = np.random.rand(177,284,3)
data = np.array(random_im)
print(data.shape)
Image.fromarray(data)
But when I am using Image.fromarray(random_array), this is throwing the following error.
Just to check if there is any issue with the shape of the array, I converted an image back to the array and converted it back after copying it to the other variable. And I got the output I was looking for.
img = np.array(Image.open('Sample_imgs/dog4.jpg'))
git = img.copy()
git.shape
Image.fromarray(git)
They both have the same shape, I don't understand where am I making the mistake.
When I am creating a 2D array and then converting it back it is giving me a black canvas of that size (even though the pixels should not be black).
random_im = np.random.randint(0,256,size=(231,177))
print(random_im)
# data = np.array(random_im)
print(data.shape)
Image.fromarray(random_im)
I was able to get this working with the solution detailed here:
import numpy as np
from PIL import Image
random_array = np.random.rand(177,284,3)
random_array = np.random.random_sample(random_array.shape) * 255
random_array = random_array.astype(np.uint8)
random_im = Image.fromarray(random_array)
random_im.show()
----EDIT
A more elegant way to get a random array of the correct type without conversions is like so:
import numpy as np
from PIL import Image
random_array = np.random.randint(low=0, high=255,size=(250,250),dtype=np.uint8)
random_im = Image.fromarray(random_array)
random_im.show()
Which is almost what you were doing in your solution, but you have to specify the dtype to be np.uint8:
random_im = np.random.randint(0,256,size=(231,177),dtype=np.uint8)
I get an image stored as an object from a camera that look like this (here reduced to make it understandable):
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
is it possible to 'import' it as an 'image' in opencv?
I tried to look at the documentation of cv2.imdecode but could get it to work.
I could preprocess this array to get it to another format but I am not sure what could 'fit' to opencv.
Thank you for your help
This is a very succinct and pythonic (using NumPy) way to implement a conversion from your hexadecimal values matrix to an RGB matrix that could be read by OpenCV.
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
def to_rgb(v):
return np.array([np.int(v[1:3],16), np.int(v[3:5],16) , np.int(v[5:7],16)])
image_cv = np.array([to_rgb(h) for h in image.flatten()]).reshape(3, 4, 3)
cv2.imwrite('result.png', image_cv)
OpenCV requires either a RGB or a BGR input, which is to say you need to give the values of Red Green Blue or Blue Green Red on a scale from 0-255 (8 bit). I have shared with you the code to convert your array to an image.
Initially, I count the number of rows to find the height in terms of pixels. Then I count the number of items in a row to find the width.
Then I create an empty array of the given dimensions using np.zeros.
I then go to each cell and convert the hex code to its RGB equivalent, using the following formula #RRGGBB, R = int(RR,16), G = int(GG, 16), B = int(BB, 16). This converts the hexadecimal string to int.
#!/usr/bin/env python3
import numpy as np
import re
import cv2
# Your image
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
# Enter the image height and width
height = int(len(image[0]))
width = int(len(image[0][0]))
# Create numpy array of BGR triplets
im = np.zeros((height,width,3), dtype=np.uint8)
for row in range (height):
for col in range(width):
hex = image[row, col][1:]
R = int(hex[0:2],16)
G = int(hex[2:4],16)
B = int(hex[4:6],16)
im[row,col] = (B,G,R)
# Save to disk
cv2.imwrite('result.png', im)
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
At the moment I'm trying to run a ConvNet. Each image, which later feeds the neural net, is stored as a list. But the list is at the moment created using three for-loops. Have a look:
im = Image.open(os.path.join(p_input_directory, item))
pix = im.load()
image_representation = []
# Get image into byte array
for color in range(0, 3):
for x in range(0, 32):
for y in range(0, 32):
image_representation.append(pix[x, y][color])
I'm pretty sure that this is not the nicest and most efficient way. Because I have to stick to the structure of the list created above, I thought about using numpy and providing an alternative way to get to the same structure.
from PIL import Image
import numpy as np
image = Image.open(os.path.join(p_input_directory, item))
image.load()
image = np.asarray(image, dtype="uint8")
image = np.reshape(image, 3072)
# Sth is missing here...
But I don't know how to reshape and concatenate the image for getting the same structure as above. Can someone help with that?
One approach would be to transpose the axes, which is essentially flattening in fortran mode i.e. reversed manner -
image = np.asarray(im, dtype="uint8")
image_representation = image.ravel('F').tolist()
For a closer look to the function have a look to the numpy.ravel documentation.
I have a device which stores a grayscale image as a series of 8 bit unsigned integer values. I want to write a python program to read these images from a file and show them using wxBitmap. I have a code that works, but it seems inefficient due to a lot of conversions between formats.
Any suggestions for a faster code are highly appreciated.
My current code:
imagearray=numpy.fromfile(file=self.f, dtype=numpy.uint8, count=npixels).reshape(Height, Width)[::-1]
pilimage = Image.fromarray(imagearray)
rgb= pilimage.convert('RGB')
rgbdata = rgb.tostring()
WxBitmap = wx.EmptyBitmap(Width,Height)
WxBitmap.CopyFromBuffer(rgbdata)
output=WxBitmap
You can get a wxBitmap directly from a numpy array.
This is an example from wxPyWiki:
import wx, numpy
def GetBitmap( self, width=32, height=32, colour = (0,0,0) ):
array = numpy.zeros( (height, width, 3),'uint8')
array[:,:,] = colour
image = wx.EmptyImage(width,height)
image.SetData( array.tostring())
wxBitmap = image.ConvertToBitmap() # OR: wx.BitmapFromImage(image)
return wxBitmap