Writing text over GIF using PIL not working - python

I'm trying to take a .GIF file, open it with PIL, and write a text on it frame by frame. However, the code only saves an image (1 frame;it doesn't move like a .GIF file.
The code:
import giphypop
from urllib import request
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
g = giphypop.Giphy()
img = g.translate("dog")
request.urlretrieve(img.media_url, "test.gif")
opened_gif = Image.open("test.gif")
opened_gif.load()
opened_gif.seek(1)
try:
while 1:
slide = opened_gif.seek(opened_gif.tell()+1)
draw = ImageDraw.Draw(slide)
# font = ImageFont.truetype(<font-file>, <font-size>)
font = ImageFont.truetype("sans-serif.ttf", 16)
# draw.text((x, y),"Sample Text",(r,g,b))
draw.text((0, 0),"Sample Text",(255,255,255),font=font)
except EOFError:
pass # end of sequence
except AttributeError:
print("Couldn't use this slide")
opened_gif.save('test_with_caption.gif')

Code from https://github.com/python-pillow/Pillow/issues/3128 that, nicely enough, solves this exact problem.
from PIL import Image, ImageDraw, ImageSequence
import io
im = Image.open('Tests/images/iss634.gif')
# A list of the frames to be outputted
frames = []
# Loop over each frame in the animated image
for frame in ImageSequence.Iterator(im):
# Draw the text on the frame
d = ImageDraw.Draw(frame)
d.text((10,100), "Hello World")
del d
# However, 'frame' is still the animated image with many frames
# It has simply been seeked to a later frame
# For our list of frames, we only want the current frame
# Saving the image without 'save_all' will turn it into a single frame image, and we can then re-open it
# To be efficient, we will save it to a stream, rather than to file
b = io.BytesIO()
frame.save(b, format="GIF")
frame = Image.open(b)
# Then append the single frame image to a list of frames
frames.append(frame)
# Save the frames as a new image
frames[0].save('out.gif', save_all=True, append_images=frames[1:])

Related

How to display Image inside a loop - Kivy

I am attempting to display images from Mysql database. I am able to load the image but can not get it to display, Target here is simply a box layout which I am referring to. This code throws an error
AttributeError: 'kivy.graphics.texture.Texture' object has no attribute 'fbind'
row = mycursor.fetchall()
target = self.ids.thebox #This is a simple boxlayout
for value in row:
if value[9]:
texture = Texture.create(size=(64, 64))
image = value[9]
binary_data = image
data = io.BytesIO(binary_data)
img = CoreImage(data, ext="png").texture
new_img = Image()
new_img.texture = img
target.add_widget(new_img.texture)
I figured this out.
First load an image in form of blob data using normal mysql python and store in a variable, my case called image, you convert it as below to data then add data to CoreImage
image = value[9]
data = io.BytesIO(image)
img = CoreImage(data, ext="png").texture
Make sure you use this for your import
from kivy.uix.image import Image, CoreImage
Next ensure you add a default image within your project folder and call it using widget as follows
widget = Image(source = 'a.jpg')
Set the texture of widget to img, then add it to your target widget or parent
widget.texture = img
target.add_widget(widget)
The full working code like below:
image = value[9] #Add your data
data = io.BytesIO(image)
img = CoreImage(data, ext="png").texture
widget = Image(source = 'a.jpg')
widget.texture = img
target.add_widget(widget)

PIL ImageTk AttributeError, can't display ImageTk on window

I've been working on a small program to convert PNG, JPG and JPEG files to .ICO files. This was relatively simple to do, but while I was trying to display the selected PNG image in Tkinter using PIL's ImageTk, I get a strange error.
from tkinter import *
from tkinter import filedialog
import re
from PIL import Image, ImageTk
root = Tk()
pathToImage = ''
selectedImage = ''
def make_square(im, min_size=256, fill_color = (0, 0, 0)): # Puts the selected image into a black square
x, y = im.size
size = max(min_size, x, y)
new_im = Image.new('RGB', (size,size), fill_color)
new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
return new_im
def select_image(): # Function that is run when Select PNG button is clicked
global pathToImage
pathToImage = filedialog.askopenfilename(filetypes=[('PNG Files','*.png'),('JPG Files','*.jpg'),('JPEG Files','*.jpeg')]) # Gets path to PNG, JPG or JPEG image
image = Image.open(pathToImage) # Opens image in PIL
image = make_square(im=image) # Turns image into square for ICO conversion
#!!!!!!!!!!!!!!!!!!!!! ERROR Among these 3 lines
global selectedImage # Here I try to tell Python I'm referring to the global variable selectedImage
selectedImage = (ImageTk.PhotoImage(image=pathToImage)) # selectedImage is given the value of ImageTk.PhotoImage with the source image being the path of the selected image
Label(root, image=selectedImage).pack() # Throws an error for some reason
# Rest of the code works fine
image.save('output.ico')
Label(root,text='Converted file stored in the same folder as \'PNG to ICO.py\'').pack()
Button(root,text='Select PNG', command=select_image).pack()
root.mainloop()
I've tried saving the image to display to a variable, but that doesn't seem to work either. Could anyone help point out what I did wrong? I'd really appreciate it.
There are couple of issues with your code.
In your line (ImageTk.PhotoImage(image=pathToImage)) you are passing a path (str) which is not what it should take, ImageTk.PhotoImage takes instance of Image(path). So change it to the image getting returned by make_square function.
Every time when the button is clicked, it'll create a new label if that's what you want then ignore this, if not then create your labels outside of the function select_image after you create the Button and later in the function update them.
I don't really get why you are using global when you can achieve your purpose without making variables pathToImage or selectedImage unless you want to access that image later in the program.
Here is the improved version of your code.
from tkinter import *
from tkinter import filedialog
import re
from PIL import Image, ImageTk
def make_square(im, min_size=256, fill_color = (0, 0, 0)): # Puts the selected image into a black square
x, y = im.size
size = max(min_size, x, y)
new_im = Image.new('RGB', (size,size), fill_color)
new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
return new_im
def select_image(): # Function that is run when Select PNG button is clicked
pathToImage = filedialog.askopenfilename(filetypes=[('PNG Files','*.png'),('JPG Files','*.jpg'),('JPEG Files','*.jpeg')])
image = Image.open(str(pathToImage)) # Opens image in PIL
image = make_square(im=image) # Turns image into square for ICO conversion
selectedImage = ImageTk.PhotoImage(image=image)
imglabel.img = selectedImage # create a reference of the image
imglabel['image'] = selectedImage
# selectedImage is given the value of ImageTk.PhotoImage with the source image being the path of the selected image
# Rest of the code works fine
image.save('output.ico', 'ICO')
infolabel['text'] = 'Converted file stored in the same folder as \'PNG to ICO.py\''
root = Tk()
but1 = Button(root,text='Select PNG', command=select_image)
but1.pack()
imglabel = Label(root)
imglabel.pack()
infolabel = Label(root)
infolabel.pack()
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.

showing video on the entire screen using OpenCV and Tkiner

I'm trying to create a GUI for playing a video that fills up the entire screen, while the button for Snapshot is still visible at the bottom.
Right now, What i manage to do is just set the app window itself to fullscreen, resulting a small sized video playing at the top and a huge "snapshot" button at the button.
Is there a way to make the video fill up the entire screen?
thanks!
from PIL import Image, ImageTk
import Tkinter as tk
import argparse
import datetime
import cv2
import os
class Application:
def __init__(self, output_path = "./"):
""" Initialize application which uses OpenCV + Tkinter. It displays
a video stream in a Tkinter window and stores current snapshot on disk """
self.vs = cv2.VideoCapture('Cat Walking.mp4') # capture video frames, 0 is your default video camera
self.output_path = output_path # store output path
self.current_image = None # current image from the camera
self.root = tk.Tk() # initialize root window
self.root.title("PyImageSearch PhotoBooth") # set window title
# self.destructor function gets fired when the window is closed
self.root.protocol('WM_DELETE_WINDOW', self.destructor)
self.panel = tk.Label(self.root) # initialize image panel
self.panel.pack(padx=10, pady=10)
# create a button, that when pressed, will take the current frame and save it to file
btn = tk.Button(self.root, text="Snapshot!", command=self.take_snapshot)
btn.pack(fill="both", expand=True, padx=10, pady=10)
# start a self.video_loop that constantly pools the video sensor
# for the most recently read frame
self.video_loop()
def video_loop(self):
""" Get frame from the video stream and show it in Tkinter """
ok, frame = self.vs.read() # read frame from video stream
if ok: # frame captured without any errors
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA
self.current_image = Image.fromarray(cv2image) # convert image for PIL
imgtk = ImageTk.PhotoImage(image=self.current_image) # convert image for tkinter
self.panel.imgtk = imgtk # anchor imgtk so it does not be deleted by garbage-collector
self.root.attributes("-fullscreen",True)
#self.oot.wm_state('zoomed')
self.panel.config(image=imgtk) # show the image
self.root.after(1, self.video_loop) # call the same function after 30 milliseconds
def take_snapshot(self):
""" Take snapshot and save it to the file """
ts = datetime.datetime.now() # grab the current timestamp
filename = "{}.jpg".format(ts.strftime("%Y-%m-%d_%H-%M-%S")) # construct filename
p = os.path.join(self.output_path, filename) # construct output path
self.current_image.save(p, "JPEG") # save image as jpeg file
print("[INFO] saved {}".format(filename))
def destructor(self):
""" Destroy the root object and release all resources """
print("[INFO] closing...")
self.root.destroy()
self.vs.release() # release web camera
cv2.destroyAllWindows() # it is not mandatory in this application
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", default="./",
help="path to output directory to store snapshots (default: current folder")
args = vars(ap.parse_args())
# start the app
print("[INFO] starting...")
pba = Application(args["output"])
pba.root.mainloop()
It's not a hard task if you don't care about execution time! We knew that resizing of an image isn't a rocket science for common user, but under the hood it takes some time to resize each frame. And if you really wonder about time and options - there're many options to play around from numpy/scipy to skimage/skvideo.
But let's try to do something with your code "as is" so we have two options to play with: cv2 and Image. For testing I grabbed 20 secs of "Keyboard Cat" video from youtube (480p) and resize each frame upto 1080p, and GUI looks like this (fullscreen 1920x1080):
Resize Methods / timeit elapsed time of showing frames:
cv2.resize() / ~81.377 s.
Image.resize() / ~82.98 s.
As you see - no big difference between theese two so here's a code (only Application class and video_loop changed):
#imports
try:
import tkinter as tk
except:
import Tkinter as tk
from PIL import Image, ImageTk
import argparse
import datetime
import cv2
import os
class Application:
def __init__(self, output_path = "./"):
""" Initialize application which uses OpenCV + Tkinter. It displays
a video stream in a Tkinter window and stores current snapshot on disk """
self.vs = cv2.VideoCapture('KeyCat.mp4') # capture video frames, 0 is your default video camera
self.output_path = output_path # store output path
self.current_image = None # current image from the camera
self.root = tk.Tk() # initialize root window
self.root.title("PyImageSearch PhotoBooth") # set window title
# self.destructor function gets fired when the window is closed
self.root.protocol('WM_DELETE_WINDOW', self.destructor)
self.root.attributes("-fullscreen", True)
# getting size to resize! 30 - space for button
self.size = (self.root.winfo_screenwidth(), self.root.winfo_screenheight() - 30)
self.panel = tk.Label(self.root) # initialize image panel
self.panel.pack(fill='both', expand=True)
# create a button, that when pressed, will take the current frame and save it to file
self.btn = tk.Button(self.root, text="Snapshot!", command=self.take_snapshot)
self.btn.pack(fill='x', expand=True)
# start a self.video_loop that constantly pools the video sensor
# for the most recently read frame
self.video_loop()
def video_loop(self):
""" Get frame from the video stream and show it in Tkinter """
ok, frame = self.vs.read() # read frame from video stream
if ok: # frame captured without any errors
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA
cv2image = cv2.resize(cv2image, self.size, interpolation=cv2.INTER_NEAREST)
self.current_image = Image.fromarray(cv2image) #.resize(self.size, resample=Image.NEAREST) # convert image for PIL
self.panel.imgtk = ImageTk.PhotoImage(image=self.current_image)
self.panel.config(image=self.panel.imgtk) # show the image
self.root.after(1, self.video_loop) # call the same function after 30 milliseconds
But you knew - do such a things "on fly" isn't a good idea, so lets try to resize all frames first and then do all stuff(only Application class and video_loop method changed, resize_video method added):
class Application:
def __init__(self, output_path = "./"):
""" Initialize application which uses OpenCV + Tkinter. It displays
a video stream in a Tkinter window and stores current snapshot on disk """
self.vs = cv2.VideoCapture('KeyCat.mp4') # capture video frames, 0 is your default video camera
...
# init frames
self.frames = self.resize_video()
self.video_loop()
def resize_video(self):
temp = list()
try:
temp_count_const = cv2.CAP_PROP_FRAME_COUNT
except AttributeError:
temp_count_const = cv2.cv.CV_CAP_PROP_FRAME_COUNT
frames_count = self.vs.get(temp_count_const)
while self.vs.isOpened():
ok, frame = self.vs.read() # read frame from video stream
if ok: # frame captured without any errors
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # convert colors from BGR to RGBA
cv2image = cv2.resize(cv2image, self.size, interpolation=cv2.INTER_NEAREST)
cv2image = Image.fromarray(cv2image) # convert image for PIL
temp.append(cv2image)
# simple progress print w/o sys import
print('%d/%d\t%d%%' % (len(temp), frames_count, ((len(temp)/frames_count)*100)))
else:
return temp
def video_loop(self):
""" Get frame from the video stream and show it in Tkinter """
if len(self.frames) != 0:
self.current_image = self.frames.pop(0)
self.panel.imgtk = ImageTk.PhotoImage(self.current_image)
self.panel.config(image=self.panel.imgtk)
self.root.after(1, self.video_loop) # call the same function after 30 milliseconds
timeit elapsed time of showing pre-resized frames: ~78.78 s.
As you see - resizing isn't a main problem of your script, but a good option!

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