Resizing PIL rgb image in tkinter application - python

background
I am creating a small slideshow application that allows for displaying an image. The original image is quite large (2000 by 2000 pixels), so I want to rescale it to fit the screen. This works as expected.
The image is essentially an array, with values ranging from 0 to 16 bit.
To get PIL to display colours, I triplicate the array, manipulate each channel individually, convert to an Image object, rescale and display. Here, the rescaling does not seem to work properly.
approach
I am sharing a barebones version of my code that repoduces this issue, at least on my system.
import tkinter as tk
import numpy as np
from PIL import Image, ImageTk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.images = {}
self.load_image()
self.show_image()
def load_image(self):
img = np.zeros((2000,2000))
img[100:800, 100:800] = 255
self.images[0] = img
def show_image(self):
img = self.images[0]
img = np.repeat(img[:,:,np.newaxis], 3, axis=2)
img = Image.fromarray(img, 'RGB')
img = img.resize((1000,1000))
img = ImageTk.PhotoImage(img)
self.persistent_img = img
self.canvas = tk.Canvas(self.parent, height=1000, width=1000)
self.canvas.grid(row=0, column=0)
self.canvas.create_image(0,0,anchor='nw', image=img)
if __name__ == '__main__':
root = tk.Tk()
MainApplication(root)
root.mainloop()
expected
In fact, this works with the code above, if I just rescale the one-channel image:
img = np.zeros((2000,2000))
img[100:800, 100:800] = 255
self.images[0] = img
[...]
img = self.images[0]
img = Image.fromarray(img)
img = img.resize((1000,1000))
[...]
actual
What is causing this, and is there a way around? Help is highly appreciated!

Should set dtype=uint8 in np.zeros(...) for image data:
np.zeros((2000, 2000), dtype=np.uint8)

Related

How to display the whole image on this canvas?

The code provided here is:
import tkinter as tk
from PIL import Image, ImageTk
from pathlib import Path
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('600x600')
self.img_path = Path(r'D:\Python\Lena.jpg')
self.img = Image.open(self.img_path)
self.img_rgb = self.img.convert('RGB')
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
self.canvas = tk.Canvas(self)
self.canvas.create_image(dim_x // 2, dim_y // 2, image=self.img_tk)
self.canvas.pack(expand=True, fill=tk.BOTH)
self.rgb_var = tk.StringVar(self, '0 0 0')
self.rgb_label = tk.Label(self, textvariable=self.rgb_var)
self.rgb_label.pack()
self.bind('<Motion>', lambda e: self.get_rgb(e))
def get_rgb(self, event):
x, y = event.x, event.y
try:
rgb = self.img_rgb.getpixel((x, y))
self.rgb_var.set(rgb)
except IndexError:
pass # ignore errors if the cursor is outside the image
if __name__ == '__main__':
app = App()
app.mainloop()
It displays an image with the RGB value of the pixel under the mouse pointer under the image (when the mouse pointer is over the image). The image used is this.
However, only the upper left quadrant of the image is displayed on the canvas. You can see that in the screenshot below.
How can I display the whole image and still have the RGB values of the pixel under the mouse pointer displayed (when the mouse pointer is over the image)?
I can see two possible solutions:
Expand image to fit window
Wrap window around image
To expand image to fit window
dim_x, dim_y = 600, 600
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
OR
To wrap window around image
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb)
Both approaches will display the entire image.
Here is the complete code with both options available via select flag.
import tkinter as tk
from PIL import Image, ImageTk
from pathlib import Path
class App(tk.Tk):
def __init__(self, select = True):
super().__init__()
self.img_path = Path('D:\Lenna.jpg')
self.img = Image.open(self.img_path)
self.img_rgb = self.img.convert('RGB')
if select:
# resize image to fit window
dim_x, dim_y = 600, 600
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
else:
# resize window to fit image
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb)
self.geometry(f'{dim_x}x{dim_y+21}')
self.canvas = tk.Canvas(self, borderwidth = 0, highlightthickness = 0)
self.canvas.create_image(0, 0, image = self.img_tk, anchor= tk.NW)
self.canvas.pack(expand=True, fill=tk.BOTH)
self.rgb_var = tk.StringVar(self, '0 0 0')
self.rgb_label = tk.Label(self, textvariable=self.rgb_var)
self.rgb_label.pack()
self.bind('<Motion>', lambda e: self.get_rgb(e))
def get_rgb(self, event):
x, y = event.x, event.y
try:
rgb = self.img_rgb.getpixel((x, y))
self.rgb_var.set(rgb)
except IndexError:
pass # ignore errors if the cursor is outside the image
if __name__ == '__main__':
app = App(False)
app.mainloop()
Everything works as expected when borderwidth and highlightthickness are removed.

How do I display resized images in tkinter using classes?

I'm trying to display a resized image, but I'm not sure how to do that using classes. This code works fine:
image = Image.open(Image_Location)
image = image.resize((200, 200), Image.ANTIALIAS)
image = ImageTk.PhotoImage(image)
But I need to do this in a class method (because I need multiple for the main project and it would take too long to do it seperately), how do I do it? This is what I have tried so far:
class Planet:
def __init__(self, name, picture):
self.name = name
self.picture = tk.PhotoImage(file=picture)
#classmethod
def resize_image(cls, picture):
image = Image.open(picture)
image = image.resize((200, 200), Image.ANTIALIAS)
image = ImageTk.PhotoImage(image)
earth = Planet("earth",Image_Location)
earth_resized = earth.resize_image(Image_Location)
test1 = Label(root, image=earth_resized)
test2 = Label(root, image=earth.picture)
test1.pack()
test2.pack()
And when I pack both labels, I get a white space (I think that is supposed to be the resized image) and the unsized image. Thanks!
You forgot to return the resized image from the method -- it should look like this:
#classmethod
def resize_image(cls, picture):
image = Image.open(picture)
image = image.resize((200, 200), Image.ANTIALIAS)
return ImageTk.PhotoImage(image)

How to create box around selected image on GUI

My GUI is a slideshow of images and there is a certain correct image on each page of the slideshow. The correct image is the top-left triangle, heart balloon, the circle, and the hippo. I am completely new to python so I have been struggling with how to create box around the selected image when a user is using the slideshow. This box forms around the correct or the incorrect image, whichever is pressed. To further explain, this includes inserting a box around an image based on where the user clicks but it does not say whether the image selected is the correct image or not. Thank you for your help
import PIL.Image
import PIL.ImageDraw
import tkinter as tk
import PIL.ImageTk
import csv
from PIL import Image
MAX_HEIGHT = 500
# height of the window (dimensions of the image)
class App(tk.Frame):
def __init__(self, imageData, master=None):
tk.Frame.__init__(self, master)
self.clickStatus = tk.StringVar()
self.loadedImages = dict()
self.master.title('Slideshow')
fram = tk.Frame(self)
tk.Button(fram, text="Previous Image", command=self.prev).pack(side=tk.LEFT)
tk.Button(fram, text=" Next Image ", command=self.next).pack(side=tk.LEFT)
tk.Label(fram, textvariable=self.clickStatus, font='Helvetica 18 bold').pack(side=tk.RIGHT)
# inside or outside
fram.pack(side=tk.TOP, fill=tk.BOTH)
self.imageLabel = tk.Label(self)
# drawing the image on the label
self.imageData = imageData
self.currentIndex = 0
# start from 0th image
self.__loadImage__()
self.imageLabel.bind("<Button-1>", self.clicked_evt)
# when you click button, it opens event of clicked_evt
self.imageLabel.pack()
self.pack()
def clicked_evt(self, evt):
x, y = evt.x, evt.y
imgData = self.loadedImages[self.imageData[self.currentIndex]['image_file']]
(l, t), (r,b) = imgData['lt'], imgData['rb']
if t<=y<=b and l<=x<=r:
##self.clickStatus.set('inside')
print('Inside')
else:
##self.clickStatus.set('outside')
print('Outside')
def __loadImage__(self):
if self.imageData[self.currentIndex]['image_file'] not in self.loadedImages:
self.im = PIL.Image.open(self.imageData[self.currentIndex]['image_file'])
ratio = MAX_HEIGHT/self.im.height
# ratio divided by existing height -> to get constant amount
height, width = int(self.im.height*ratio), int(self.im.width * ratio)
# calculate the new h and w and then resize next
self.im = self.im.resize((width, height))
lt = (int(self.imageData[self.currentIndex]['left']*ratio), int(self.imageData[self.currentIndex]['top']*ratio))
rb = (int(self.imageData[self.currentIndex]['right']*ratio), int(self.imageData[self.currentIndex]['bottom']*ratio))
# modifying new ratios with new height and width
#shape = [lt, rb]
# print(shape)
#img1 = PIL.ImageDraw.Draw(self.im)
#img1.rectangle(shape, outline ="red")
if self.im.mode == "1":
self.img = PIL.ImageTk.BitmapImage(self.im, foreground="white")
else:
self.img = PIL.ImageTk.PhotoImage(self.im)
imgData = self.loadedImages.setdefault(self.imageData[self.currentIndex]['image_file'], dict())
imgData['image'] = self.img
imgData['lt'] = lt
imgData['rb'] = rb
# for next and previous so it loads the same image adn don't do calculations again
self.img = self.loadedImages[self.imageData[self.currentIndex]['image_file']]['image']
self.imageLabel.config(image=self.img, width=self.img.width(), height=self.img.height())
def prev(self):
self.currentIndex = (self.currentIndex+len(self.imageData) - 1 ) % len(self.imageData)
self.__loadImage__()
# here if i go to the first one and press back, goes to last, round robbin
def next(self):
self.currentIndex = (self.currentIndex + 1) % len(self.imageData)
self.__loadImage__()
# here if i go to the last one and press next, goes to first, round robbin
def loadData(fname):
with open(fname, mode='r') as f:
reader = csv.DictReader(f)
data = [dict(row) for row in reader]
for row in data:
row['top'], row['bottom'], row['left'], row['right'] = int(row['top']),int(row['bottom']),int(row['left']),int(row['right'])
return data
if __name__ == "__main__":
data = loadData('bounding_box.csv')
app = App(data)
app.mainloop()
As far as I understand you simply want to draw a rectangle around an image when it is clicked (explanation in code comments):
from tkinter import Tk, Canvas
from PIL import Image, ImageTk
# below code is used to create images, you can also load them from a file if you need
mode = 'RGB'
size = (150, 150)
color_lst = ['red', 'green', 'blue', 'yellow']
# best to get all the images for the slide in a single list for further easier workings
img_lst = [Image.new(mode, size, color) for color in color_lst]
# selecting function that will be called when user clicks on image
def select(id_):
canvas.create_rectangle(canvas.bbox(id_), width=5)
# here you will put other code to be executed after
# user clicks on the image like going to the next frame or sth
# additionally you could use this if you want the other rectangles to disappear
# but kinda pointless if you will switch frames and stuff
# for canvas_id in canvas_images:
# if canvas_id == id_:
# continue
# canvas.create_rectangle(canvas.bbox(canvas_id), width=5, outline='white')
root = Tk()
# here create a photoimage list from the above image list
photo_lst = [ImageTk.PhotoImage(image) for image in img_lst]
# create canvas
canvas = Canvas(root, width=400, height=400)
canvas.pack()
# set the coordinates for the four images
coordinates = [(100, 100), (300, 100), (100, 300), (300, 300)]
# create the images and append their id to a list
canvas_images = [
canvas.create_image(pos[0], pos[1], image=photo) for pos, photo in zip(coordinates, photo_lst)
]
# bind the ids from `canvas_images` to being clicked and execute simple drawing method
for c_img in canvas_images:
canvas.tag_bind(
c_img, '<Button-1>', lambda e, i=c_img: select(i)
)
root.mainloop()

How Tkinter could load png images with transparent backgrounds?

Some weeks ago i load a png image into my Tkinter Canvas and drawed with create_image, but now i can't do this anymore, i tried convert with ImageTk but png did not display
I have the following code:
load = Image.open("mouse.png")
self.img = ImageTk.PhotoImage(load)
self.draw.create_image(100,100,image=self.img,anchor = NW)
self.draw.image = self.img
I just need to present a png image
Try this
vSmallIco = (15, 15)
self.original = Image.open('.//data//img//plus.png')
resized = self.original.resize(vSmallIco, Image.ANTIALIAS)
self.plusIco = ImageTk.PhotoImage(resized)
self.medIco = ttk.Label(self.mf, image=self.plusIco, style='Main.TLabel')
this is done with this import
from PIL import Image, ImageTk
Also, please use a *.png image if you wish to use png.

Face Detection OpenCV Python GUI

I'm new in the Python GUI world. I'm developing a Python project to detect faces in a webcam and take a picture of the face, that part is already set up. I've been searching and testing codes for the GUI part, I tried with PyQt and PySide but I didn't have succed. I found a code from Adrian at PyImageSearch which have a frame for the webcam and a button to capture the image and use Tkinter for the GUI.
from __future__ import print_function
from PIL import Image
from PIL import ImageTk
import Tkinter as tki
import threading
import datetime
import imutils
import cv2
import os
class PhotoBoothApp:
def __init__(self, vs, outputPath):
# store the video stream object and output path, then initialize
# the most recently read frame, thread for reading frames, and
# the thread stop event
self.vs = vs
self.outputPath = outputPath
self.frame = None
self.thread = None
self.stopEvent = None
# initialize the root window and image panel
self.root = tki.Tk()
self.panel = None
# create a button, that when pressed, will take the current
# frame and save it to file
btn = tki.Button(self.root, text="Snapshot!",
command=self.takeSnapshot)
btn.pack(side="bottom", fill="both", expand="yes", padx=10,
pady=10)
# start a thread that constantly pools the video sensor for
# the most recently read frame
self.stopEvent = threading.Event()
self.thread = threading.Thread(target=self.videoLoop, args=())
self.thread.start()
# set a callback to handle when the window is closed
self.root.wm_title("PyImageSearch PhotoBooth")
self.root.wm_protocol("WM_DELETE_WINDOW", self.onClose)
def videoLoop(self):
# DISCLAIMER:
# I'm not a GUI developer, nor do I even pretend to be. This
# try/except statement is a pretty ugly hack to get around
# a RunTime error that Tkinter throws due to threading
try:
# keep looping over frames until we are instructed to stop
while not self.stopEvent.is_set():
# grab the frame from the video stream and resize it to
# have a maximum width of 300 pixels
self.frame = self.vs.read()
self.frame = imutils.resize(self.frame, width=300)
# OpenCV represents images in BGR order; however PIL
# represents images in RGB order, so we need to swap
# the channels, then convert to PIL and ImageTk format
image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image)
image = ImageTk.PhotoImage(image)
# if the panel is not None, we need to initialize it
if self.panel is None:
self.panel = tki.Label(image=image)
self.panel.image = image
self.panel.pack(side="left", padx=10, pady=10)
# otherwise, simply update the panel
else:
self.panel.configure(image=image)
self.panel.image = image
except RuntimeError, e:
print("[INFO] caught a RuntimeError")
def takeSnapshot(self):
# grab the current timestamp and use it to construct the
# output path
ts = datetime.datetime.now()
filename = "{}.jpg".format(ts.strftime("%Y-%m-%d_%H-%M-%S"))
p = os.path.sep.join((self.outputPath, filename))
# save the file
cv2.imwrite(p, self.frame.copy())
print("[INFO] saved {}".format(filename))
def onClose(self):
# set the stop event, cleanup the camera, and allow the rest of
# the quit process to continue
print("[INFO] closing...")
self.stopEvent.set()
self.vs.stop()
self.root.quit()
Now the part for the detection face I think it should go in the videoLoop function and I added the face detection code to it so the function looks like this
try:
# keep looping over frames until we are instructed to stop
while not self.stopEvent.is_set():
# grab the frame from the video stream and resize it to
# have a maximum width of 300 pixels
self.frame = self.vs.read()
self.frame = imutils.resize(self.frame, width=300)
# OpenCV represents images in BGR order; however PIL
# represents images in RGB order, so we need to swap
# the channels, then convert to PIL and ImageTk format
detector = cv2.CascadeClassifier("C:\Proyectos\Python\GUI\tkinter-photo-booth\haarcascade_frontalface_default.xml")
image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
# load the cat detector Haar cascade, then detect cat faces
# in the input image
rects = detector.detectMultiScale(image, scaleFactor=1.5,
minNeighbors=5, minSize=(30, 30))
# loop over the cat faces and draw a rectangle surrounding each
for (i, (x, y, w, h)) in rects:
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
image = Image.fromarray(image)
image = ImageTk.PhotoImage(image)
# if the panel is not None, we need to initialize it
if self.panel is None:
self.panel = tki.Label(image=image)
self.panel.image = image
self.panel.pack(side="left", padx=10, pady=10)
# otherwise, simply update the panel
else:
self.panel.configure(image=image)
self.panel.image = image
But when I run the code, the rectangles to frame the face doesn't appear. I don't really know if the code to detect faces should go in the voidLoop function or if it should go in the snapshot function. I've already ask to Adrian in his webpage but I'm searching for extra help. Thanks in advance

Categories