Posts: 18
Threads: 5
Joined: May 2021
I'm back again with my camera app! Trying to overlay/merge two images when saving them.
The error i get is Error: AttributeError: 'PhotoImage' object has no attribute 'load'
Please see code below:
from tkinter import *
import cv2
from PIL import Image, ImageTk, ImageEnhance
import time
from tkinter import filedialog
import numpy as np
class App:
def __init__(self, video_source=0):
self.overlay_img = None
self.appName = "Kamera"
self.window = Tk()
self.window.title(self.appName)
self.window.resizable(0, 0)
# self.window.wm_iconbitmap("cam.ico")
self.window['bg'] = 'black'
self.video_source = video_source
self.vid = MyVideoCapture(self.video_source)
# self.label = Label(self.window, text=self.appName, font=15, bg='blue', fg='white').pack(side=TOP, fill=BOTH)
self.canvas = Canvas(self.window, width=self.vid.width, height=self.vid.height, bg='red')
self.canvas.pack()
self.btn_snapshot = Button(self.window, text="Snapshot", width=5, command=self.snapshot)
self.btn_snapshot.pack(side=LEFT, padx=10)
self.btn_overlay = Button(self.window, text="Overlay", width=7, command=self.overlay)
self.btn_overlay.pack(side=LEFT, padx=10)
self.btn_settings = Button(self.window, text="Settings", width=5, command=self.settings)
self.btn_settings.pack(side=LEFT, padx=10)
self.slide_value = 200
self.update()
self.window.mainloop()
def settings(self):
self.newWindow = Toplevel(self.window)
self.newWindow.title("Settings")
self.newWindow.geometry("400x400")
self.btn_flip = Button(self.newWindow, text="Mirror Image", width=10, command=self.flip_img)
self.btn_flip.pack(side=LEFT, padx=10)
# self.brightness_lbl = Label(self.newWindow, text="Image Brightness")
# self.brightness_lbl.pack(anchor=NW)
#var = IntVar()
self.brightness = Scale(
self.newWindow, length=200, from_=0, to=255, orient=HORIZONTAL, label="Image Brightness",
command=self.slide)
self.brightness.set(200)
self.brightness.pack(anchor=NW)
def slide(self, var):
self.slide_value = self.brightness.get()
print(self.slide_value)
def flip_img(self):
self.vid.flipped = not self.vid.flipped
def overlay(self):
file = filedialog.askopenfile(
mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
if file:
self.overlay_img = ImageTk.PhotoImage(file=file)
def snapshot(self):
isTrue, frame = self.vid.getFrame()
if isTrue:
filename = filedialog.asksaveasfilename(
defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
# image = "IMG-" + time.strftime("%H-%M-%S-%d-%m") + ".jpg"
cv2.imwrite(filename, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
# msg = Label(self.window, text='Image Saved' + image, bg='black', fg='green').place(x=430, y=510)
else:
messagebox.showerror("paint says", "unable to save image ,\n something went wrong")
if isTrue and self.overlay_img:
self.photo.paste(self.overlay_img, (0, 0))
self.stamped_img = ImageTk.PhotoImage(self.photo)
filename = filedialog.asksaveasfilename(
defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
filename = self.stamped_img
def update(self):
isTrue, frame = self.vid.getFrame()
frame = cv2.normalize(frame, frame, 0, self.slide_value, cv2.NORM_MINMAX)
if isTrue:
self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame))
#self.canvas.tag_lower(self.photo)
self.canvas.create_image(0, 0, image=self.photo, anchor=NW)
if self.overlay_img:
#self.canvas.tag_raise(self.overlay_img)
self.canvas.create_image(0,0, image=self.overlay_img, anchor=NW)
#self.stamped_img = self.photo.paste(self.overlay_img)
self.window.after(100, self.update)
class MyVideoCapture:
def __init__(self, video_source=0):
self.vid = cv2.VideoCapture(video_source)
if not self.vid.isOpened():
raise ValueError("Unable to open this camera \n select another video source", video_source)
self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.flipped = True
def getFrame(self):
if self.vid.isOpened():
isTrue, frame = self.vid.read()
if isTrue and self.flipped:
frame = cv2.flip(frame, 1)
if isTrue:
return (isTrue, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
else:
return (isTrue, None)
else:
return (isTrue, None)
def __del__(self):
if self.vid.isOpened():
self.vid.release()
if __name__ == "__main__":
App()
Posts: 1,583
Threads: 3
Joined: Mar 2020
There is no load in your code, and you haven't included the entire traceback (which would show where the error is being produced). Is this all the code?
Posts: 2,168
Threads: 35
Joined: Sep 2016
Hello again, I don't see load used directly anywhere it must be called indirectly.
You should show the full error traceback as it indicates where it went wrong and more information.
Posts: 18
Threads: 5
Joined: May 2021
(May-31-2021, 10:31 PM)Yoriz Wrote: Hello again, I don't see load used directly anywhere it must be called indirectly.
You should show the full error traceback as it indicates where it went wrong and more information.
It's in ImageTk.py:
Error: File "/home/kieran/PycharmProjects/Kamera/venv/lib/python3.8/site-packages/PIL/ImageTk.py", line 165, in paste
im.load()
AttributeError: 'PhotoImage' object has no attribute 'load'
ImageTk.py
#
# The Python Imaging Library.
# $Id$
#
# a Tk display interface
#
# History:
# 96-04-08 fl Created
# 96-09-06 fl Added getimage method
# 96-11-01 fl Rewritten, removed image attribute and crop method
# 97-05-09 fl Use PyImagingPaste method instead of image type
# 97-05-12 fl Minor tweaks to match the IFUNC95 interface
# 97-05-17 fl Support the "pilbitmap" booster patch
# 97-06-05 fl Added file= and data= argument to image constructors
# 98-03-09 fl Added width and height methods to Image classes
# 98-07-02 fl Use default mode for "P" images without palette attribute
# 98-07-02 fl Explicitly destroy Tkinter image objects
# 99-07-24 fl Support multiple Tk interpreters (from Greg Couch)
# 99-07-26 fl Automatically hook into Tkinter (if possible)
# 99-08-15 fl Hook uses _imagingtk instead of _imaging
#
# Copyright (c) 1997-1999 by Secret Labs AB
# Copyright (c) 1996-1997 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
import tkinter
from io import BytesIO
from . import Image
# --------------------------------------------------------------------
# Check for Tkinter interface hooks
_pilbitmap_ok = None
def _pilbitmap_check():
global _pilbitmap_ok
if _pilbitmap_ok is None:
try:
im = Image.new("1", (1, 1))
tkinter.BitmapImage(data=f"PIL:{im.im.id}")
_pilbitmap_ok = 1
except tkinter.TclError:
_pilbitmap_ok = 0
return _pilbitmap_ok
def _get_image_from_kw(kw):
source = None
if "file" in kw:
source = kw.pop("file")
elif "data" in kw:
source = BytesIO(kw.pop("data"))
if source:
return Image.open(source)
# --------------------------------------------------------------------
# PhotoImage
class PhotoImage:
"""
A Tkinter-compatible photo image. This can be used
everywhere Tkinter expects an image object. If the image is an RGBA
image, pixels having alpha 0 are treated as transparent.
The constructor takes either a PIL image, or a mode and a size.
Alternatively, you can use the ``file`` or ``data`` options to initialize
the photo image object.
:param image: Either a PIL image, or a mode string. If a mode string is
used, a size must also be given.
:param size: If the first argument is a mode string, this defines the size
of the image.
:keyword file: A filename to load the image from (using
``Image.open(file)``).
:keyword data: An 8-bit string containing image data (as loaded from an
image file).
"""
def __init__(self, image=None, size=None, **kw):
# Tk compatibility: file or data
if image is None:
image = _get_image_from_kw(kw)
if hasattr(image, "mode") and hasattr(image, "size"):
# got an image instead of a mode
mode = image.mode
if mode == "P":
# palette mapped data
image.load()
try:
mode = image.palette.mode
except AttributeError:
mode = "RGB" # default
size = image.size
kw["width"], kw["height"] = size
else:
mode = image
image = None
if mode not in ["1", "L", "RGB", "RGBA"]:
mode = Image.getmodebase(mode)
self.__mode = mode
self.__size = size
self.__photo = tkinter.PhotoImage(**kw)
self.tk = self.__photo.tk
if image:
self.paste(image)
def __del__(self):
name = self.__photo.name
self.__photo.name = None
try:
self.__photo.tk.call("image", "delete", name)
except Exception:
pass # ignore internal errors
def __str__(self):
"""
Get the Tkinter photo image identifier. This method is automatically
called by Tkinter whenever a PhotoImage object is passed to a Tkinter
method.
:return: A Tkinter photo image identifier (a string).
"""
return str(self.__photo)
def width(self):
"""
Get the width of the image.
:return: The width, in pixels.
"""
return self.__size[0]
def height(self):
"""
Get the height of the image.
:return: The height, in pixels.
"""
return self.__size[1]
def paste(self, im, box=None):
"""
Paste a PIL image into the photo image. Note that this can
be very slow if the photo image is displayed.
:param im: A PIL image. The size must match the target region. If the
mode does not match, the image is converted to the mode of
the bitmap image.
:param box: A 4-tuple defining the left, upper, right, and lower pixel
coordinate. See :ref:`coordinate-system`. If None is given
instead of a tuple, all of the image is assumed.
"""
# convert to blittable
im.load()
image = im.im
if image.isblock() and im.mode == self.__mode:
block = image
else:
block = image.new_block(self.__mode, im.size)
image.convert2(block, image) # convert directly between buffers
tk = self.__photo.tk
try:
tk.call("PyImagingPhoto", self.__photo, block.id)
except tkinter.TclError:
# activate Tkinter hook
try:
from . import _imagingtk
try:
if hasattr(tk, "interp"):
# Required for PyPy, which always has CFFI installed
from cffi import FFI
ffi = FFI()
# PyPy is using an FFI CDATA element
# (Pdb) self.tk.interp
# <cdata 'Tcl_Interp *' 0x3061b50>
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
else:
_imagingtk.tkinit(tk.interpaddr(), 1)
except AttributeError:
_imagingtk.tkinit(id(tk), 0)
tk.call("PyImagingPhoto", self.__photo, block.id)
except (ImportError, AttributeError, tkinter.TclError):
raise # configuration problem; cannot attach to Tkinter
# --------------------------------------------------------------------
# BitmapImage
class BitmapImage:
"""
A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
expects an image object.
The given image must have mode "1". Pixels having value 0 are treated as
transparent. Options, if any, are passed on to Tkinter. The most commonly
used option is ``foreground``, which is used to specify the color for the
non-transparent parts. See the Tkinter documentation for information on
how to specify colours.
:param image: A PIL image.
"""
def __init__(self, image=None, **kw):
# Tk compatibility: file or data
if image is None:
image = _get_image_from_kw(kw)
self.__mode = image.mode
self.__size = image.size
if _pilbitmap_check():
# fast way (requires the pilbitmap booster patch)
image.load()
kw["data"] = f"PIL:{image.im.id}"
self.__im = image # must keep a reference
else:
# slow but safe way
kw["data"] = image.tobitmap()
self.__photo = tkinter.BitmapImage(**kw)
def __del__(self):
name = self.__photo.name
self.__photo.name = None
try:
self.__photo.tk.call("image", "delete", name)
except Exception:
pass # ignore internal errors
def width(self):
"""
Get the width of the image.
:return: The width, in pixels.
"""
return self.__size[0]
def height(self):
"""
Get the height of the image.
:return: The height, in pixels.
"""
return self.__size[1]
def __str__(self):
"""
Get the Tkinter bitmap image identifier. This method is automatically
called by Tkinter whenever a BitmapImage object is passed to a Tkinter
method.
:return: A Tkinter bitmap image identifier (a string).
"""
return str(self.__photo)
def getimage(photo):
"""Copies the contents of a PhotoImage to a PIL image memory."""
im = Image.new("RGBA", (photo.width(), photo.height()))
block = im.im
photo.tk.call("PyImagingPhotoGet", photo, block.id)
return im
def _show(image, title):
"""Helper for the Image.show method."""
class UI(tkinter.Label):
def __init__(self, master, im):
if im.mode == "1":
self.image = BitmapImage(im, foreground="white", master=master)
else:
self.image = PhotoImage(im, master=master)
super().__init__(master, image=self.image, bg="black", bd=0)
if not tkinter._default_root:
raise OSError("tkinter not initialized")
top = tkinter.Toplevel()
if title:
top.title(title)
UI(top, image).pack()
Posts: 2,168
Threads: 35
Joined: Sep 2016
Jun-01-2021, 04:40 PM
(This post was last modified: Jun-01-2021, 04:40 PM by Yoriz.)
I think due to Namespace flooding with * imports you've learnt your lesson in why to not do it.
The code is expecting a PIL Image instead I think its got a tkinter Image which has no load method see the tk Image class below.
class Image:
"""Base class for images."""
_last_id = 0
def __init__(self, imgtype, name=None, cnf={}, master=None, **kw):
self.name = None
if not master:
master = _get_default_root('create image')
self.tk = getattr(master, 'tk', master)
if not name:
Image._last_id += 1
name = "pyimage%r" % (Image._last_id,) # tk itself would use image<x>
if kw and cnf: cnf = _cnfmerge((cnf, kw))
elif kw: cnf = kw
options = ()
for k, v in cnf.items():
if callable(v):
v = self._register(v)
options = options + ('-'+k, v)
self.tk.call(('image', 'create', imgtype, name,) + options)
self.name = name
def __str__(self): return self.name
def __del__(self):
if self.name:
try:
self.tk.call('image', 'delete', self.name)
except TclError:
# May happen if the root was destroyed
pass
def __setitem__(self, key, value):
self.tk.call(self.name, 'configure', '-'+key, value)
def __getitem__(self, key):
return self.tk.call(self.name, 'configure', '-'+key)
def configure(self, **kw):
"""Configure the image."""
res = ()
for k, v in _cnfmerge(kw).items():
if v is not None:
if k[-1] == '_': k = k[:-1]
if callable(v):
v = self._register(v)
res = res + ('-'+k, v)
self.tk.call((self.name, 'config') + res)
config = configure
def height(self):
"""Return the height of the image."""
return self.tk.getint(
self.tk.call('image', 'height', self.name))
def type(self):
"""Return the type of the image, e.g. "photo" or "bitmap"."""
return self.tk.call('image', 'type', self.name)
def width(self):
"""Return the width of the image."""
return self.tk.getint(
self.tk.call('image', 'width', self.name)) Try removing from tkinter import * and using import tkinter as tk from now on.
Posts: 18
Threads: 5
Joined: May 2021
Hi Yoriz!
I tried the above and subsequently had to add tk. before various objects such as Canvas and Button Still getting the same error:
Error: /home/kieran/PycharmProjects/Kamera/venv/bin/python /home/kieran/PycharmProjects/Kamera/Kamera.py
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1892, in __call__
return self.func(*args)
File "/home/kieran/PycharmProjects/Kamera/Kamera.py", line 80, in snapshot
self.photo.paste(self.overlay_img, (0, 0))
File "/home/kieran/PycharmProjects/Kamera/venv/lib/python3.8/site-packages/PIL/ImageTk.py", line 165, in paste
im.load()
AttributeError: 'PhotoImage' object has no attribute 'load'
Process finished with exit code 0
Could this be a potential answer (which I don't fully understand): https://stackoverflow.com/questions/4013...il-imagetk
My current code:
import tkinter as tk
import cv2
from PIL import Image, ImageTk, ImageEnhance
import time
from tkinter import filedialog
import numpy as np
class App:
def __init__(self, video_source=0):
self.overlay_img = None
self.appName = "Kamera"
self.window = tk.Tk()
self.window.title(self.appName)
self.window.resizable(0, 0)
# self.window.wm_iconbitmap("cam.ico")
self.window['bg'] = 'black'
self.video_source = video_source
self.vid = MyVideoCapture(self.video_source)
# self.label = Label(self.window, text=self.appName, font=15, bg='blue', fg='white').pack(side=TOP, fill=BOTH)
self.canvas = tk.Canvas(self.window, width=self.vid.width, height=self.vid.height, bg='red')
self.canvas.pack()
self.btn_snapshot = tk.Button(self.window, text="Snapshot", width=5, command=self.snapshot)
self.btn_snapshot.pack(side=tk.LEFT, padx=10)
self.btn_overlay = tk.Button(self.window, text="Overlay", width=7, command=self.overlay)
self.btn_overlay.pack(side=tk.LEFT, padx=10)
self.btn_settings = tk.Button(self.window, text="Settings", width=5, command=self.settings)
self.btn_settings.pack(side=tk.LEFT, padx=10)
self.slide_value = 200
self.update()
self.window.mainloop()
def settings(self):
self.newWindow = Toplevel(self.window)
self.newWindow.title("Settings")
self.newWindow.geometry("400x400")
self.btn_flip = Button(self.newWindow, text="Mirror Image", width=10, command=self.flip_img)
self.btn_flip.pack(side=LEFT, padx=10)
# self.brightness_lbl = Label(self.newWindow, text="Image Brightness")
# self.brightness_lbl.pack(anchor=NW)
#var = IntVar()
self.brightness = Scale(
self.newWindow, length=200, from_=0, to=255, orient=HORIZONTAL, label="Image Brightness",
command=self.slide)
self.brightness.set(200)
self.brightness.pack(anchor=NW)
def slide(self, var):
self.slide_value = self.brightness.get()
print(self.slide_value)
def flip_img(self):
self.vid.flipped = not self.vid.flipped
def overlay(self):
file = filedialog.askopenfile(
mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
if file:
self.overlay_img = ImageTk.PhotoImage(file=file)
def snapshot(self):
isTrue, frame = self.vid.getFrame()
if isTrue:
filename = filedialog.asksaveasfilename(
defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
# image = "IMG-" + time.strftime("%H-%M-%S-%d-%m") + ".jpg"
cv2.imwrite(filename, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
# msg = Label(self.window, text='Image Saved' + image, bg='black', fg='green').place(x=430, y=510)
else:
messagebox.showerror("paint says", "unable to save image ,\n something went wrong")
if isTrue and self.overlay_img:
self.photo.paste(self.overlay_img, (0, 0))
self.stamped_img = ImageTk.PhotoImage(self.photo)
filename = filedialog.asksaveasfilename(
defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
filename = self.stamped_img
def update(self):
isTrue, frame = self.vid.getFrame()
frame = cv2.normalize(frame, frame, 0, self.slide_value, cv2.NORM_MINMAX)
if isTrue:
self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame))
#self.canvas.tag_lower(self.photo)
self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)
if self.overlay_img:
#self.canvas.tag_raise(self.overlay_img)
self.canvas.create_image(0,0, image=self.overlay_img, anchor=tk.NW)
#self.stamped_img = self.photo.paste(self.overlay_img)
self.window.after(100, self.update)
class MyVideoCapture:
def __init__(self, video_source=0):
self.vid = cv2.VideoCapture(video_source)
if not self.vid.isOpened():
raise ValueError("Unable to open this camera \n select another video source", video_source)
self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.flipped = True
def getFrame(self):
if self.vid.isOpened():
isTrue, frame = self.vid.read()
if isTrue and self.flipped:
frame = cv2.flip(frame, 1)
if isTrue:
return (isTrue, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
else:
return (isTrue, None)
else:
return (isTrue, None)
def __del__(self):
if self.vid.isOpened():
self.vid.release()
if __name__ == "__main__":
App()
Posts: 2,168
Threads: 35
Joined: Sep 2016
O  I thought that was going to fix it but it's good that you got rid of the * imports
self.overlay_img = ImageTk.PhotoImage(file=file) self.overlay_img is a Tkinter-compatible photo image
self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame)) self.photo is also a Tkinter-compatible photo image
When calling paste I think the passed in image has to be an actual PIL image.
self.photo.paste(self.overlay_img, (0, 0))
as self.overlay_img is used in the update method You probably need to keep self.overlay_img as it is
def update(self):
...
...
if self.overlay_img:
#self.canvas.tag_raise(self.overlay_img)
self.canvas.create_image(0,0, image=self.overlay_img, anchor=NW)
Here is what I hope will fix it
When you make self.overlay_img try also making a pill image
def overlay(self):
file = filedialog.askopenfile(
mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
if file:
self.overlay_img = ImageTk.PhotoImage(file=file)
self.pil_overlay_img = Image.open(file) Then change
if isTrue and self.overlay_img:
self.photo.paste(self.overlay_img, (0, 0)) to
if isTrue and self.pil_overlay_img:
self.photo.paste(self.pil_overlay_img, (0, 0))
Posts: 6,774
Threads: 20
Joined: Feb 2020
This is from the documentation for PIL ImageTk module.
https://pillow.readthedocs.io/en/stable/...ageTk.html
Quote:paste(im, box=None)[source]
Paste a PIL image into the photo image. Note that this can be very slow if the photo image is displayed.
Parameters
im – A PIL image. The size must match the target region. If the mode does not match, the image is converted to the mode of the bitmap image.
box – A 4-tuple defining the left, upper, right, and lower pixel coordinate. See Coordinate System. If None is given instead of a tuple, all of the image is assumed.
Notice that im is a PIL image, not an ImageTk.PhotoImage. You need to load your overlay using Quote:PIL.Image.open(fp, mode='r', formats=None)
Posts: 18
Threads: 5
Joined: May 2021
Lesson learned on imports
Unfortunately neither of the above work. I don't get an error but the image saves without the overlay on it.
Posts: 2,168
Threads: 35
Joined: Sep 2016
The error is gone, that's progress
Try it with both images being pillow images
Paste another image into an image with Python, Pillow
|